From 3563b51295ae22a481cb9f8749d9d3d17b57b78f Mon Sep 17 00:00:00 2001 From: setrofim Date: Wed, 23 Aug 2023 11:31:35 +0100 Subject: [PATCH 001/110] Add support for missing go-cose signing algorithms Add support for ES384, ES512, EdDSA, PS256, PS384, and PS512, which are supported by the go-cose library but were not being exposed here. Signed-off-by: setrofim --- corim/signedcorim_test.go | 124 ++++++++++++++++++++++++++++++++------ corim/signer.go | 46 ++++++++++++-- 2 files changed, 145 insertions(+), 25 deletions(-) diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 755812c4..9e030b3c 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -13,7 +13,7 @@ import ( ) var ( - testECKey = []byte(`{ + testES256Key = []byte(`{ "kty": "EC", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", @@ -22,6 +22,82 @@ var ( "use": "enc", "kid": "1" }`) + + testES384Key = []byte(`{ + "kty": "EC", + "d": "XiZ_ZEDMw3Hr9BjNc_4qbNxMG6VpkFHTN3KcdT1UlOc51pFwS1t6Yg_aFYJTGMBf", + "use": "sig", + "crv": "P-384", + "x": "Ay-c_vlONI_FNQn4PNHXwEswuoxOTqOEHNIQbSKv5OnC_KBLwAbg5uBQRHCRmFnu", + "y": "mJpRrG-ex0R08heh1qm-osCH7SSTKC1Bjx1SrFpUQZCiYQXdPLIokC0DGRAMYq41", + "alg": "ES384" + }`) + + testES512Key = []byte(`{ + "use": "sig", + "kty": "EC", + "kid": "Xt7n2MSHsgErmf1Uq-UZV451DhzlSPVuH75Rj9adAZ0", + "crv": "P-521", + "alg": "ES512", + "x": "AVBBp8Mckn-HYsdx5bMSkFRxGhKH2M7ked49PqK2PzG2A5QEBPc813AHUO3MHoe-_JQjEm-r-E52sNln-zn6OFJL", + "y": "AcsVxiDaIJpr3MToPmDqSjWnCkg765Rum3DWuFNaTmvietwrY6OYdoW995m4SkWv4GYI0mdchuXoThvPn0CXcDb9", + "d": "AeSLG30MsuX6wzm-AYpBbTooVPt3GvU_Fl9LesAFZrtJ4HJhPL3QhMLmiDbB3Am0j_IpIR3P9dTJTNpt6B_YSVda" + }`) + + testEdDSAKey = []byte(`{ + "use": "sig", + "kty": "OKP", + "kid": "RBx2781Ag7Sd1vmuVbxpe0LzWT94pmB3GPtNx6m_gsQ", + "crv": "Ed25519", + "alg": "EdDSA", + "x": "JL3cmVCzN3m3afnctG2agbjb6nrZWFl48A8Feknkpx0", + "d": "m8LDAfKvGWAZTXWC21tzHeSYLqVSP4YpzI-Z7fL3NEY" + }`) + + testPS256Key = []byte(`{ + "use": "sig", + "kty": "RSA", + "kid": "jYGw-iPMi7AxzdJPMHYh_gb9YI-BQGAVAvf6hgZndzw", + "alg": "PS256", + "n": "r8tDvmXtJjtGOgX34bxDGT3-v2AtfVkP4vhdOl5Wau-XFyaPNpob5u3DtNsYUnHREQFnrPbIp02IeassUqSi6FlT9SZsYX8M5xkfpCuLb6FD5Loqz4ZMhzqtMNoKjUt2_9tdyW-iMfMm-EWLfVRfiXnfXq__o122LZ93-zmR4kEusCp7rUa12-E48pv4Wu5CwKntz08DjP-WB-yR8ZT1_F4IacqK6Uhhdh56TLONoUytyQkJTYi0lvohzVVtuRp7jXDpG9TBMBsyAJ0yj6FvpA-Bs1mkMNUlUr-p6xbSIAsOrv4FBtLXDKtApurRQmnNAtm4LTE4RsuQxI2FSKlOnQ", + "e": "AQAB", + "d": "bx6bObUQDISXRYIUSDpKZ6BKcQoIdx1e72dy9rw-_-VmqhmTmT4cuQI-HQoI-8Q6FPfAYxKzjx1xUQckQzESULB2Y5XgGFjI_SNiXtGvl-ZmFiSffwIzSZ-Lbj_FP78d_2jYhcXszooWbgT3wUceBLZmvWGew9MunvQYUVL4pfzktRn7zX0u9ks8GYxNfnwbeB8e8x7ZGrGpPSy5MNJkTHuPpu6XGXR8fJFEEFZZdsyJYd-Ii5Nma2uXyVZfBeRYmqlRIvok5jcNGmFm9wM291v7fieuJycSV71iFQnZfoF48uiNt3mGsdzPNfulCSKjMdR03jk-v1YyyQP34wu8uQ", + "p": "ww7iRrQKk37YmQP_4xVtdAtOj5-bBWkM6wid2VNDss3u3GbivCqchqY2fQFgw9wKVYN0T7hS8wgErKPgE7ALTImwrK66TdTLZ_ljLScoYcrHRdAnTiqSbK3iyUnCs15ptSzOSXJHUXeVbynK7K9wo6TALz8c7-y05Gc_XpvM4I8", + "q": "5reZTbuRXJ3O2sIG4vBvmn0UujZ9WbnvzQ36c92vxIqsWZ1MJzzc-9FKv9iG3zHS8tLLLYT4V6InIovJ6ZNgit0HieFyWfGNfc-3rt13OZwcFLhAu5nizZzkh24Mx0lquXoRxQwgc43Fg0Lk64C-xhgWAhW6OeNIxwp3zxpLHBM", + "dp": "MfOK0M5kcvcl4rGagv3GxNPsb21RFqabT0kqmy_ug0inZbvXTpae9QB1rbd_n0inQNTkIVIzs9cW01s4E_KeQiB0pRQt06at3FeKJVMEzV5Pf7pZhnPygXBaRm_kM2j3KxVpUne4ec1k8E3EkK4w60dSjAbekzaL8H3cRY8ifVM", + "dq": "oe6PDP3vIqAoRWYVS0cSLc5ItAH2rPlSFAwRky0vZrUmDqfWgVu4ho349vnUf-cKdh_5NvOzEl7fNOIET4p_IjfMSLwRdIuTkZAvDe6m9apaEzjXRlTV2RabV2qoUV94JsJEopbGWBRTYrOa1KhCPes91yzEzkh2Fi2Etblwqj0", + "qi": "tWSsmBzj2NZ71Phe6wOUSKvdvLFyASRgnF-YZN1VKsyXER-SPujBiSjHC5f0TCcNKg4Y1GZxRjfVin2_ZO_qmlOij3xc6GsxVdXXvyzKg1wamf-Fut0I2K6dGLalJuCY0rp_Q1BGLv-LET2wArEkMgYWtsQLRuZXCktMi9XPsiU" + }`) + + testPS384Key = []byte(`{ + "use": "sig", + "kty": "RSA", + "kid": "-Q8rUDoeh60B1OorMT6v80KxBTy0SzqeAab2Isi09Ko", + "alg": "PS384", + "n": "6aeDsQF3WjbJEBq00Oxne7m9CNXGUn4ANI4DsjYDDfZnewNiZDhnO14g-VvrnF5MoP725Ho9b2VSQZ5Ke0Bp8mtvIXohsCtzodNe7_dcU9ycD-PdLBRZalvloLKt_o8-rVdWEpiJtg8CO4VZHu2ReOX-mzecr4W8RxBiQGRaP6t1XAUwj3bQnkHYcQ9VtD3-RxRS9vr1xDUv-v8HrmlUtrlBTAABIzDUS8YXP5h2hjGfd8qaY1c-4TvKAdNuasTCWUjJjVhj_-6pA0bmxM0F2jEdZo-1yA0vZE0V6hBeayMCTOw08wPdOVC07dupZMFYBiRdH1jL_eGwBaCTp2QIEw", + "e": "AQAB", + "d": "6FTnP8Rjd1LujpLfpLbNF1vTOcvHjhM4BQoJZtUKKIIQ12LAHUNwcrngM9NQ7oVd0OB1gy6BlBi9t_27td6Q-roVIMaeZNxv-EODLT1bkw_UJoC_VatOVdHW_PluxaaN_jLPpWID3QIDiEfKHFTBx-N6TcD4jhd-5XLHH5wpmQ_wE6G55PJmq0jONCntK5c4D21CAePzT2FL3oJ4OmpsZqChWAmYm1Lv2OFzSQpaI0HjE6Cchlkx_8ENKhEJrfxkLCNA1yPUfkXGOkpF6GFVpjTSVYjnwyYJKeC5D5KDb6Ln_n56OFejksuzCcKJCkDEKsS7UZmxw02DbYgjjCcsgQ", + "p": "9ygG1oK4M9_ZJyMdAoDQFIkc4ISekZAm1_y8PW-NR0_l-bZkP2SUtRXBiT0qEtg1ltkVSCLRRtd940HLuOu7ZKOoF0DRvzFlkyrKyqwCwjGSBhqiSdC9PW9YGjYabIRzJaXBvp_F4v6u3KV1aIJDx9hrlAEJcUtzW1EHYB_GWuE", + "q": "8gPO5_MpV2J6WVXVmFfYJ6TJvPLkM0oHfticCH3wkj3E6NgCygOkWe8SuFEWb2FJJ8AwBXCLp-j7gN2oU-TylqFd_Of2oQb4YjTJAwvub-kJuN8Y434C1DAEdSEqNGyvMAxMKE-q89DtzDmDKd5v5gI9Ci4DCQdl27q0ddks1XM", + "dp": "17zyqxATpgRBUu5Nhj_WYfaFZF2e5ETGA0azMZVL5vGRNvXEb6lmPOMuupLPRP_BV1lKQFtT_dhgJJzsLRBn1KMeOJ31-EQv-9Qgi-S1y7jlU7qv6mrwpM2qQ8byLcM3l6cmhTSF0WyqSiOLZpw-ehUpYlm9Wk2X9h-2pmtWA0E", + "dq": "GsirHGZ_28jtS3fBZNPL-080eHHVKYv22mX0lsgBWN33LeHCJUNT7BQWWUm4FumIZBrT9bYn7pRNSUy-tVIwOtVvBm9Rjy6rTIsU9_5ZDA-ZYNln8r1eaMdLpv7doeGpXcLupsNyYvtrZd-zkW2pqqXyxW6kLVqhPjkigaxgVts", + "qi": "jH5q55Ez6f3yQE3-PEf3a6i7C1PgHfAy6E3sXONoyztbCMm6hSj_agBUj2bt_Q9XeddQf66OmBa2zPGGJqp0lclSDwuJB82iZspHjsAQuUQaBWE2eOfJlJkU4L34ibahQT2DgRxuosvv9gCSIXMOh7fZ2hp71XExrQTuA2BIC_s" + }`) + + testPS512Key = []byte(`{ + "use": "sig", + "kty": "RSA", + "kid": "umrdA9anNl7Qewgx-QWa7iBeVBHJ_i50NXdAiLLoU80", + "alg": "PS512", + "n": "yAdpoRbB0CPVjBFoVr4k4bEvrtpxLYDiqtGBwWxSEBlXxSLfABIgQXpR2Dkhda9Wsk-kBOEXS-8MUs8Slq0f6g0oaG1X_3IbZOcI_QzKDcIiQnoJH5herrY8S0eLkcUWvmAHuQoJ7G6JIiMePuM6eLNagwJzSIr6qq4y7-3ua1ZEieJXUkhsHEL3_ZsIWEIZ1tkI6nrfZnqRc1OMXqxRgeZTI5nqF99UptxUwGoTKGQB-yiH9kVGyYqjgvOQFCVLOyUMbdN76BAKGduQ56vjfNqqRNtZm_E1S7dhyu9F86C5p97BswRcFjkCb499bDEvYofJHY7npBnmJMRBhF1krw", + "e": "AQAB", + "d": "Qd5XBUnmJrE2J_qvfij9Iijjx9N9A3v2qEN3VAdkepKt2WfjQTW203kBLI-bmhJUHUGmhEjPEB021KoFuAJoiP0uOj0PhjnAFZkS16l3e9Jaz8M57-KQAz5VWoDD0AuzspsSz_cjT20S0V_5HMJcxdRh0NRkvBWv97aHZYTXRxa6SAZnyElAE2IqkLkKFRyK63V8TXJNFUc9DRzDtKgDszpZ7QflMqqb0nh02VImrcZdyRQtHEyCyCZvUURzL44qMayYWVmlDNwjXsS37iZLP0kValfe6sjNrS8JCq52ccyD_PBGTDv6aDDatrUdg64aEcNFvKzbRdQKxlMY9U8G2Q", + "p": "_ohOSzzBHPycHTtZUOXR4b-DVfDtdLLxtiPBQUdcWvu_TL9HFkjRdx3Y-YaW6WRFAirF6dDz1VixhQFgnwlbRkYW2U8VB1wlYmVPOnJvDWO7aWVfJ8_QZjfhdfnQya_ATYu-X8o71yo0eP6GLRGNs6a_1L13o_2P8Dtg0FOKVws", + "q": "yS6ooWwNOuEKLZUmLLc-euxaVxEOabKwRWoxTzbSaIeTR0MfhTDWWBVjWqKp6wDtN-3gFTnC56oUwddsktTxKvpQHj49CHCGPPUpOFROJoqRrjnxi4cd35oPkbbcoujMacSCLygKpNRZb7eqnVe8pW5aFIXUYqN5hjA1MxlEH20", + "dp": "5xAKM1bV4GCZwBeufzgCjjLzIUNz7Oq9bqGKwJ3tg1LiWOOTvvEf5kicPfkman1yAAOgYyAjGlxH2vxjIDy4NVVPTLrz1hiaf3aEtARKOBd_fLBf755CC2lTLWw5U75Ojpb7na3TIQLZW7WDTMqQnrQTlSbiw2ZeErF0s-oCvf0", + "dq": "NJQlLkr3CjRWXKNmXrllcurikW67vZQdzYZ7bKB_TSJhs3YvfrfMzSiJ1t48WlbbqIpazjFSZwlkc2TB034jqX_SAJVzjgkajEPmifo-koQUntw17KlbfVzeRM7tywXcpqfc_kYQwhNdbH0r8gNEIlg84rA3WbAvyoo-3SP1UeE", + "qi": "pCqnuy1DLWTyNXW3pihPkhIXHPMBWBR6kjXGoQ_QG8ig_ukWEs5CVxMXBNid8zOEklzMOCIShK03n8o5U60tAjOztzB_cSoNsKSLLuO2lRQKIOTjfI4I4QY9eY3lvfXCSt40DH6YPXX3fOgy6b52WDNdOdu1BK6AQ5JYgMdLttw" + }`) ) func TestSignedCorim_FromCOSE_ok(t *testing.T) { @@ -255,33 +331,43 @@ func metaGood(t *testing.T) *Meta { } func TestSignedCorim_SignVerify_ok(t *testing.T) { - signer, err := NewSignerFromJWK(testECKey) - require.NoError(t, err) + for _, key := range [][]byte{ + testES256Key, + testES384Key, + testES512Key, + testEdDSAKey, + testPS256Key, + testPS384Key, + testPS512Key, + } { + signer, err := NewSignerFromJWK(key) + require.NoError(t, err) - var SignedCorimIn SignedCorim + var SignedCorimIn SignedCorim - SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorim) - SignedCorimIn.Meta = *metaGood(t) + SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorim) + SignedCorimIn.Meta = *metaGood(t) - cbor, err := SignedCorimIn.Sign(signer) - assert.Nil(t, err) + cbor, err := SignedCorimIn.Sign(signer) + assert.Nil(t, err) - var SignedCorimOut SignedCorim + var SignedCorimOut SignedCorim - fmt.Printf("signed-corim: %x\n", cbor) + fmt.Printf("signed-corim: %x\n", cbor) - err = SignedCorimOut.FromCOSE(cbor) - assert.Nil(t, err) + err = SignedCorimOut.FromCOSE(cbor) + assert.Nil(t, err) - pk, err := NewPublicKeyFromJWK(testECKey) - require.NoError(t, err) + pk, err := NewPublicKeyFromJWK(key) + require.NoError(t, err) - err = SignedCorimOut.Verify(pk) - assert.Nil(t, err) + err = SignedCorimOut.Verify(pk) + assert.Nil(t, err) + } } func TestSignedCorim_SignVerify_fail_tampered(t *testing.T) { - signer, err := NewSignerFromJWK(testECKey) + signer, err := NewSignerFromJWK(testES256Key) require.NoError(t, err) var SignedCorimIn SignedCorim @@ -303,7 +389,7 @@ func TestSignedCorim_SignVerify_fail_tampered(t *testing.T) { err = SignedCorimOut.FromCOSE(cbor) assert.Nil(t, err) - pk, err := NewPublicKeyFromJWK(testECKey) + pk, err := NewPublicKeyFromJWK(testES256Key) require.NoError(t, err) // ... but the signature verification fails @@ -312,7 +398,7 @@ func TestSignedCorim_SignVerify_fail_tampered(t *testing.T) { } func TestSignedCorim_Sign_fail_bad_corim(t *testing.T) { - signer, err := NewSignerFromJWK(testECKey) + signer, err := NewSignerFromJWK(testES256Key) require.NoError(t, err) var SignedCorimIn SignedCorim diff --git a/corim/signer.go b/corim/signer.go index 1151e92f..9d6d15b4 100644 --- a/corim/signer.go +++ b/corim/signer.go @@ -3,7 +3,9 @@ package corim import ( "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" + "crypto/rsa" "fmt" "reflect" @@ -11,8 +13,9 @@ import ( cose "github.com/veraison/go-cose" ) +const noAlg = cose.Algorithm(-65537) + func getAlgAndKeyFromJWK(j []byte) (cose.Algorithm, crypto.Signer, error) { - const noAlg = cose.Algorithm(-65537) var ( err error k jwk.Key @@ -34,12 +37,17 @@ func getAlgAndKeyFromJWK(j []byte) (cose.Algorithm, crypto.Signer, error) { switch v := key.(type) { case *ecdsa.PrivateKey: - crv = v.Curve - if crv == elliptic.P256() { - alg = cose.AlgorithmES256 - break + alg = ellipticCurveToAlg(v.Curve) + if alg == noAlg { + return noAlg, nil, fmt.Errorf("unknown elliptic curve %v", crv) + } + case ed25519.PrivateKey: + alg = cose.AlgorithmEd25519 + case *rsa.PrivateKey: + alg = rsaJWKToAlg(k) + if alg == noAlg { + return noAlg, nil, fmt.Errorf("unknown RSA algorithm %q", k.Algorithm().String()) } - return noAlg, nil, fmt.Errorf("unknown elliptic curve %v", crv) default: return noAlg, nil, fmt.Errorf("unknown private key type %v", reflect.TypeOf(key)) } @@ -47,6 +55,32 @@ func getAlgAndKeyFromJWK(j []byte) (cose.Algorithm, crypto.Signer, error) { return alg, key, nil } +func ellipticCurveToAlg(c elliptic.Curve) cose.Algorithm { + switch c { + case elliptic.P256(): + return cose.AlgorithmES256 + case elliptic.P384(): + return cose.AlgorithmES384 + case elliptic.P521(): + return cose.AlgorithmES512 + default: + return noAlg + } +} + +func rsaJWKToAlg(k jwk.Key) cose.Algorithm { + switch k.Algorithm().String() { + case "PS256": + return cose.AlgorithmPS256 + case "PS384": + return cose.AlgorithmPS384 + case "PS512": + return cose.AlgorithmPS512 + default: + return noAlg + } +} + func NewSignerFromJWK(j []byte) (cose.Signer, error) { alg, key, err := getAlgAndKeyFromJWK(j) if err != nil { From 3db1945f81b1c966a812ed381eab0549b75e35bc Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Thu, 31 Aug 2023 12:13:15 +0100 Subject: [PATCH 002/110] Fix Marshal for CCA Platform Config Signed-off-by: Sam Davis --- comid/rawvalue.go | 16 ++++++++++++++-- comid/rawvalue_test.go | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/comid/rawvalue.go b/comid/rawvalue.go index 3d4440b1..140b5f8c 100644 --- a/comid/rawvalue.go +++ b/comid/rawvalue.go @@ -82,10 +82,22 @@ func (o *RawValue) UnmarshalJSON(data []byte) error { } func (o RawValue) MarshalJSON() ([]byte, error) { + var ( + v tnv + b []byte + err error + ) + switch t := o.val.(type) { case TaggedRawValueBytes: - return json.Marshal(t) + b, err = json.Marshal(o.val) + if err != nil { + return nil, err + } + v = tnv{Type: "bytes", Value: b} default: - return nil, fmt.Errorf("unknown type %T for $raw-value-type-choice", t) + return nil, fmt.Errorf("unknown type %T for raw-value-type-choice", t) } + + return json.Marshal(v) } diff --git a/comid/rawvalue_test.go b/comid/rawvalue_test.go index 966f5725..d890794f 100644 --- a/comid/rawvalue_test.go +++ b/comid/rawvalue_test.go @@ -32,3 +32,14 @@ func TestRawValue_Get_Bytes_nok(t *testing.T) { _, err = rv.GetBytes() assert.EqualError(t, err, expectedErr) } + +func TestRawValue_Marshal_UnMarshal_ok(t *testing.T) { + tv := RawValue{} + rv := tv.SetBytes([]byte{0x01, 0x02, 0x03}) + bytes, err := rv.MarshalJSON() + assert.NoError(t, err) + sv := RawValue{} + err = sv.UnmarshalJSON(bytes) + assert.NoError(t, err) + assert.Equal(t, *rv, sv) +} From 73b101cc8ce4de441c55085bae7939a54da5e743 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Mon, 4 Sep 2023 05:22:26 -0400 Subject: [PATCH 003/110] Fix failing tests Signed-off-by: Yogesh Deshpande --- comid/example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comid/example_test.go b/comid/example_test.go index bd3197f9..50aa19b9 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -114,7 +114,7 @@ func Example_encode() { // Output: // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff030a04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff030b04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81a20075464748496a6b69736c646e415344787657592e2e2e0182744d454e477364686675676a5157457479582e2e2e744d4949456e6a43434134616741774942412e2e2e038182a101d902264702deadbeefdead81a200744d49476b416745424244436b3551626f422e2e2e0182744d4949446b6a43434178696741774942412e2e2e744d4949456e6a43434134616741774942412e2e2e - // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32:q83vAA==","sha-256-32://///w=="],"op-flags":["notSecure","debug"],"raw-value":"AQIDBA==","raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32:q83vAA==","sha-256-32://///w=="],"op-flags":["notConfigured","notSecure","debug"],"raw-value":"AQIDBA==","raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"key":"FGHIjkisldnASDxvWY...","chain":["MENGsdhfugjQWEtyX...","MIIEnjCCA4agAwIBA..."]}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"key":"MIGkAgEBBDCk5QboB...","chain":["MIIDkjCCAxigAwIBA...","MIIEnjCCA4agAwIBA..."]}]}]}} + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32:q83vAA==","sha-256-32://///w=="],"op-flags":["notSecure","debug"],"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32:q83vAA==","sha-256-32://///w=="],"op-flags":["notConfigured","notSecure","debug"],"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"key":"FGHIjkisldnASDxvWY...","chain":["MENGsdhfugjQWEtyX...","MIIEnjCCA4agAwIBA..."]}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"key":"MIGkAgEBBDCk5QboB...","chain":["MIIDkjCCAxigAwIBA...","MIIEnjCCA4agAwIBA..."]}]}]}} } func Example_encode_PSA() { From 8e672f07c895f84b760eff705244bd0bce29a5dc Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Tue, 29 Aug 2023 08:29:48 +0100 Subject: [PATCH 004/110] .gitignore: add artifacts built by tests Add test-generated binaries to .gitignore. Signed-off-by: Sergei Trofimov --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 06b53509..6bb26a80 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ # vendor/ mocks/ + +# Generated during testing +cocli/cocli +cocli/cmd/output.cbor From 16eb57a4f901cf89005664e0b976455438aab9c7 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Tue, 29 Aug 2023 11:10:30 +0100 Subject: [PATCH 005/110] cocli: add remote service auth support Add support for remote service authentication to the `corim submit` sub-command. Associated with it is support for loading configuration from a file (note: technically this was already present but was kind of broken and wasn't actually used for any configuration.) Signed-off-by: Sergei Trofimov --- cocli/README.md | 20 +- cocli/cmd/corimSubmit.go | 26 +- cocli/cmd/corimSubmit_test.go | 2 + cocli/cmd/isubmitter.go | 6 +- cocli/cmd/root.go | 81 +- cocli/data/config/example-config.yaml | 21 + go.mod | 8 +- go.sum | 1037 ++++++++++++++++++++++++- 8 files changed, 1164 insertions(+), 37 deletions(-) create mode 100644 cocli/data/config/example-config.yaml diff --git a/cocli/README.md b/cocli/README.md index 81b95a2f..28c1395b 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -423,10 +423,10 @@ Tags: ### Submit Use the `corim submit` subcommand to upload a CoRIM using the Veraison provisioning API. -The CoRIM file containing the CoRIM data in CBOR format is supplied via the -`--corim-file` switch (abbrev. `-f`). The server URL where to upload the CoRIM +The CoRIM file containing the CoRIM data in CBOR format is supplied via the +`--corim-file` switch (abbrev. `-f`). The server URL where to upload the CoRIM payload is supplied via the `--api-server` switch (abbrev. `-s`). -Further, it is required to supply the media type of the content via the +Further, it is required to supply the media type of the content via the `--media-type` switch (abbrev. `-m`) ``` $ cocli corim submit \ @@ -437,6 +437,16 @@ $ cocli corim submit \ >> "corim.cbor" submit ok ``` +#### Remote Service Authentication + +The above will work if the remote service does not authenticate +endorsement-provisioning API calls. If the service does authenticate, then +cocli must be configured appropriately. This can be done using a `config.yaml` +file located in the current working directory, or in the standard config +path (usually `~/.config/cocli/config.yaml` on XDG-compliant systems). Please +see `./data/config/example-config.yaml` file for details of the configuration +that needs to be provided. + ### Extract CoSWIDs, CoMIDs and CoTSs Use the `corim extract` subcommand to extract the embedded CoMIDs, CoSWIDs and CoTSs @@ -561,7 +571,7 @@ graph LR cliComidCreate --> CBORComid1 cliCotsCreate --> CBORCots1 cliCoswidCreate --> CBORSwid1 - + cliCorimCreate --> CBORCorim cliCorimSign --> CoseSign1 cliCorimVerify --> signBool @@ -585,4 +595,4 @@ graph LR cliCorimExtract --> CBORComid2 cliCorimExtract --> CBORSwid2 cliCorimExtract --> CBORCots2 -``` \ No newline at end of file +``` diff --git a/cocli/cmd/corimSubmit.go b/cocli/cmd/corimSubmit.go index beab0354..86e262b1 100644 --- a/cocli/cmd/corimSubmit.go +++ b/cocli/cmd/corimSubmit.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/veraison/apiclient/provisioning" ) @@ -33,7 +34,7 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command { To submit the CBOR-encoded CoRIM from file "unsigned-corim.cbor" with media type "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" to the Veraison provisioning API endpoint "https://veraison.example/endorsement-provisioning/v1", do: - + cocli corim submit \ --corim-file=unsigned-corim.cbor \ @@ -64,6 +65,27 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command { apiServer = cmd.Flags().StringP("api-server", "s", "", "API server where to submit the corim file") mediaType = cmd.Flags().StringP("media-type", "m", "", "media type of the CoRIM file") + cmd.Flags().VarP(&authMethod, "auth", "a", + `authentication method, must be one of "none"/"passthrough", "basic", "oauth2"`) + cmd.Flags().StringP("client-id", "C", "", "OAuth2 client ID") + cmd.Flags().StringP("client-secret", "S", "", "OAuth2 client secret") + cmd.Flags().StringP("token-url", "T", "", "token URL of the OAuth2 service") + cmd.Flags().StringP("username", "U", "", "service username") + cmd.Flags().StringP("password", "P", "", "service password") + + err := viper.BindPFlag("auth", cmd.Flags().Lookup("auth")) + cobra.CheckErr(err) + err = viper.BindPFlag("client_id", cmd.Flags().Lookup("client-id")) + cobra.CheckErr(err) + err = viper.BindPFlag("client_secret", cmd.Flags().Lookup("client-secret")) + cobra.CheckErr(err) + err = viper.BindPFlag("username", cmd.Flags().Lookup("username")) + cobra.CheckErr(err) + err = viper.BindPFlag("password", cmd.Flags().Lookup("password")) + cobra.CheckErr(err) + err = viper.BindPFlag("token_url", cmd.Flags().Lookup("token-url")) + cobra.CheckErr(err) + return cmd } @@ -87,6 +109,8 @@ func checkSubmitArgs() error { } func provisionData(data []byte, submitter ISubmitter, uri string, mediaType string) error { + submitter.SetAuth(cliConfig.Auth) + if err := submitter.SetSubmitURI(uri); err != nil { return fmt.Errorf("unable to set submit URI: %w", err) } diff --git a/cocli/cmd/corimSubmit_test.go b/cocli/cmd/corimSubmit_test.go index a4ecca46..5fa29d20 100644 --- a/cocli/cmd/corimSubmit_test.go +++ b/cocli/cmd/corimSubmit_test.go @@ -131,6 +131,7 @@ func Test_CorimSubmitCmd_submit_ok(t *testing.T) { fs = afero.NewMemMapFs() err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644) require.NoError(t, err) + ms.EXPECT().SetAuth(gomock.Any()) ms.EXPECT().SetSubmitURI("http://veraison.example/endorsement-provisioning/v1/submit").Return(nil) ms.EXPECT().SetDeleteSession(true) ms.EXPECT().Run(testSignedCorimValid, "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1").Return(nil) @@ -155,6 +156,7 @@ func Test_CorimSubmitCmd_submit_not_ok(t *testing.T) { fs = afero.NewMemMapFs() err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644) require.NoError(t, err) + ms.EXPECT().SetAuth(gomock.Any()) ms.EXPECT().SetSubmitURI("http://veraison.example/endorsement-provisioning/v1/submit").Return(nil) ms.EXPECT().SetDeleteSession(true) err = errors.New(`unexpected HTTP response code 404`) diff --git a/cocli/cmd/isubmitter.go b/cocli/cmd/isubmitter.go index 532981f2..823e0ac8 100644 --- a/cocli/cmd/isubmitter.go +++ b/cocli/cmd/isubmitter.go @@ -1,10 +1,14 @@ package cmd -import "github.com/veraison/apiclient/common" +import ( + "github.com/veraison/apiclient/auth" + "github.com/veraison/apiclient/common" +) type ISubmitter interface { Run([]byte, string) error SetClient(client *common.Client) error + SetAuth(a auth.IAuthenticator) SetSubmitURI(uri string) error SetDeleteSession(session bool) } diff --git a/cocli/cmd/root.go b/cocli/cmd/root.go index ec617023..efe8c153 100644 --- a/cocli/cmd/root.go +++ b/cocli/cmd/root.go @@ -4,18 +4,24 @@ package cmd import ( + "errors" "fmt" "os" + "path/filepath" "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/spf13/viper" + + "github.com/veraison/apiclient/auth" ) var ( cfgFile string fs = afero.NewOsFs() + + cliConfig = &ClientConfig{} + authMethod = auth.MethodPassthrough ) // rootCmd represents the base command when called without any subcommands @@ -27,6 +33,10 @@ var rootCmd = &cobra.Command{ SilenceErrors: true, } +type ClientConfig struct { + Auth auth.IAuthenticator +} + func Execute() { cobra.CheckErr(rootCmd.Execute()) } @@ -34,27 +44,70 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $XDG_CONFIG_HOME/cocli/config.yaml)") } // initConfig reads in config file and ENV variables if set func initConfig() { - if cfgFile != "" { - viper.SetConfigFile(cfgFile) - } else { - home, err := os.UserHomeDir() + v, err := readConfig(cfgFile) + cobra.CheckErr(err) + + err = authMethod.Set(v.GetString("auth")) + cobra.CheckErr(err) + + switch authMethod { + case auth.MethodPassthrough: + cliConfig.Auth = &auth.NullAuthenticator{} + case auth.MethodBasic: + cliConfig.Auth = &auth.BasicAuthenticator{} + err = cliConfig.Auth.Configure(map[string]interface{}{ + "username": v.GetString("username"), + "password": v.GetString("password"), + }) + cobra.CheckErr(err) + case auth.MethodOauth2: + cliConfig.Auth = &auth.Oauth2Authenticator{} + err = cliConfig.Auth.Configure(map[string]interface{}{ + "client_id": v.GetString("client_id"), + "client_secret": v.GetString("client_secret"), + "token_url": v.GetString("token_url"), + "username": v.GetString("username"), + "password": v.GetString("password"), + }) cobra.CheckErr(err) + default: + // Should never get here as authMethod value is set via + // Method.Set(), which ensures that it's one of the above. + panic(fmt.Sprintf("unknown auth method: %q", authMethod)) + } +} - // search config in home directory with name ".cli" (without extension) - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".cli") +func readConfig(path string) (*viper.Viper, error) { + v := viper.GetViper() + if path != "" { + v.SetConfigFile(path) + } else { + wd, err := os.Getwd() + if err != nil { + return nil, err + } + + userConfigDir, err := os.UserConfigDir() + if err == nil { + v.AddConfigPath(filepath.Join(userConfigDir, "cocli")) + } + v.AddConfigPath(wd) + v.SetConfigType("yaml") + v.SetConfigName("config") } - viper.AutomaticEnv() // read in environment variables that match + v.SetEnvPrefix("cocli") + v.AutomaticEnv() - // if a config file is found, read it in - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + err := v.ReadInConfig() + if errors.As(err, &viper.ConfigFileNotFoundError{}) { + err = nil } + + return v, err } diff --git a/cocli/data/config/example-config.yaml b/cocli/data/config/example-config.yaml new file mode 100644 index 00000000..6b1c47f8 --- /dev/null +++ b/cocli/data/config/example-config.yaml @@ -0,0 +1,21 @@ +# This file contains example configuration for connecting to Veraison services. +# This configuration is only necessary for the `cocli corim submit` sub-command, +# as that is the only instance where remote service configuration is used. You +# do not need this configuration for creating or manipulating corims/corim and +# related objects locally. + +# Authentication method used by the remote service. +auth: none # may also be "basic" or "oauth2" + +# Credentials for the remote service. +username: example_user # used only if auth is "basic" or "oauth2" +password: Passw0rd! # used only if auth is "basic" or "oauth2"; this can also + # be specfied on the command line using --password, or by + # setting COCLI_PASSWORD environment variable + +# OAuth2 cofiguration for the authorisation server associated with the remote +# service. +client_id: veraison-client # used only if auth is "oauth2" +client_secret: YifmabB4cVSPPtFLAmHfq7wKaEHQn10Z # used only if auth is "oauth2" +token_url: http://localhost:11111/realms/veraison/protocol/openid-connect/token # used only if auth is "oauth2" + diff --git a/go.mod b/go.mod index 5730b4f1..3fe5919b 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,12 @@ require ( github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.0 github.com/lestrrat-go/jwx/v2 v2.0.8 - github.com/spf13/afero v1.6.0 + github.com/spf13/afero v1.9.2 github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.9.0 - github.com/stretchr/testify v1.8.1 - github.com/veraison/apiclient v0.0.2 + github.com/stretchr/testify v1.8.2 + github.com/veraison/apiclient v0.2.0 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff github.com/veraison/go-cose v1.0.0-rc.1 github.com/veraison/swid v1.1.0 - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.3.8 // indirect ) diff --git a/go.sum b/go.sum index fc8b5a2b..b78c681b 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -23,38 +25,607 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -62,16 +633,28 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -79,9 +662,18 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -90,17 +682,30 @@ github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -128,9 +733,13 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -143,11 +752,16 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -157,20 +771,44 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -200,6 +838,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -208,15 +847,24 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -229,6 +877,9 @@ github.com/lestrrat-go/jwx/v2 v2.0.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9H github.com/lestrrat-go/jwx/v2 v2.0.8/go.mod h1:zLxnyv9rTlEvOUHbc48FAfIL8iYu2hHvIRaTFGc8mT0= github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -239,8 +890,12 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -251,25 +906,43 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moogar0880/problems v0.1.1 h1:bktLhq8NDG/czU2ZziYNigBFksx13RaYe5AVdNmHDT4= +github.com/moogar0880/problems v0.1.1/go.mod h1:5Dxrk2sD7BfBAgnOzQ1yaTiuCYdGPUh49L8Vhfky62c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -277,8 +950,10 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -302,12 +977,14 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/veraison/apiclient v0.0.2 h1:S/BG22tt5ewsBRi4A9Z7BH4MCznLNtHkRhWIHu9xmuA= -github.com/veraison/apiclient v0.0.2/go.mod h1:H8YDx1ixM24GYP/aLbhq+HJsej0lVUqFCRIL5Uu4B0o= +github.com/veraison/apiclient v0.2.0 h1:QELvZ+eEfzh9v0ORe9B2UTMpiA7aONHpZIfwSfcRR6s= +github.com/veraison/apiclient v0.2.0/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A= +github.com/veraison/cmw v0.1.0/go.mod h1:WoBrlgByc6C1FeHhdze1/bQx1kv5d1sWKO5ezEf4Hs4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff h1:r6I2eJL/z8dp5flsQIKHMeDjyV6UO8If3MaVBLvTjF4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= github.com/veraison/go-cose v1.0.0-rc.1 h1:4qA7dbFJGvt7gcqv5MCIyCQvN+NpHFPkW7do3EeDLb8= @@ -321,7 +998,10 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -332,7 +1012,10 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= @@ -344,22 +1027,45 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -383,7 +1089,12 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -417,13 +1128,38 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -440,6 +1176,23 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -451,7 +1204,12 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -494,12 +1252,15 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -507,13 +1268,50 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -523,13 +1321,25 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -544,6 +1354,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -574,21 +1385,39 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -616,13 +1445,48 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -659,10 +1523,13 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -676,6 +1543,95 @@ google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKr google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= +google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -701,6 +1657,22 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -715,10 +1687,17 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= @@ -739,6 +1718,42 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From fc51bb6791530683d6c79f0edd4ee9fda052ce82 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Tue, 29 Aug 2023 11:35:55 +0100 Subject: [PATCH 006/110] cocli: allow specifying api-server via config file Make api-server configurable via the config file, as well as the command line flag. This removes the need to specify --api-server flag on each corim submit sub-command invocation (given that this setting is going to be fairly static for a particular setup). Signed-off-by: Sergei Trofimov --- cocli/cmd/corimSubmit.go | 16 ++++++++++------ cocli/data/config/example-config.yaml | 3 +++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cocli/cmd/corimSubmit.go b/cocli/cmd/corimSubmit.go index 86e262b1..f231d18e 100644 --- a/cocli/cmd/corimSubmit.go +++ b/cocli/cmd/corimSubmit.go @@ -16,8 +16,8 @@ import ( var ( corimFile *string - apiServer *string mediaType *string + apiServer string ) var ( @@ -54,7 +54,7 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command { return fmt.Errorf("read CoRIM payload failed: %w", err) } - if err = provisionData(data, submitter, *apiServer, *mediaType); err != nil { + if err = provisionData(data, submitter, apiServer, *mediaType); err != nil { return fmt.Errorf("submit CoRIM payload failed reason: %w", err) } return nil @@ -62,9 +62,9 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command { } corimFile = cmd.Flags().StringP("corim-file", "f", "", "name of the CoRIM file in CBOR format") - apiServer = cmd.Flags().StringP("api-server", "s", "", "API server where to submit the corim file") mediaType = cmd.Flags().StringP("media-type", "m", "", "media type of the CoRIM file") + cmd.Flags().StringP("api-server", "s", "", "API server where to submit the corim file") cmd.Flags().VarP(&authMethod, "auth", "a", `authentication method, must be one of "none"/"passthrough", "basic", "oauth2"`) cmd.Flags().StringP("client-id", "C", "", "OAuth2 client ID") @@ -73,7 +73,9 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command { cmd.Flags().StringP("username", "U", "", "service username") cmd.Flags().StringP("password", "P", "", "service password") - err := viper.BindPFlag("auth", cmd.Flags().Lookup("auth")) + err := viper.BindPFlag("api_server", cmd.Flags().Lookup("api-server")) + cobra.CheckErr(err) + err = viper.BindPFlag("auth", cmd.Flags().Lookup("auth")) cobra.CheckErr(err) err = viper.BindPFlag("client_id", cmd.Flags().Lookup("client-id")) cobra.CheckErr(err) @@ -93,10 +95,12 @@ func checkSubmitArgs() error { if corimFile == nil || *corimFile == "" { return errors.New("no CoRIM input file supplied") } - if apiServer == nil || *apiServer == "" { + + apiServer = viper.GetString("api_server") + if apiServer == "" { return errors.New("no API server supplied") } - u, err := url.Parse(*apiServer) + u, err := url.Parse(apiServer) if err != nil || !u.IsAbs() { return fmt.Errorf("malformed API server URL") } diff --git a/cocli/data/config/example-config.yaml b/cocli/data/config/example-config.yaml index 6b1c47f8..d067efbc 100644 --- a/cocli/data/config/example-config.yaml +++ b/cocli/data/config/example-config.yaml @@ -4,6 +4,9 @@ # do not need this configuration for creating or manipulating corims/corim and # related objects locally. +# API Server submit endpoint URL. +api_server: https://veraison.example/endorsement-provisioning/v1/submit + # Authentication method used by the remote service. auth: none # may also be "basic" or "oauth2" From 6e8cc9c377b7a90e9dbaf1b2c80f0276c6bd6b87 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 7 Sep 2023 07:59:19 +0100 Subject: [PATCH 007/110] signer: change AlgorithmEd25519 -> AlgorithmEdDSA The former name is deprecated in the latest go-cose. Signed-off-by: Sergei Trofimov --- corim/signer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corim/signer.go b/corim/signer.go index 9d6d15b4..04182d60 100644 --- a/corim/signer.go +++ b/corim/signer.go @@ -42,7 +42,7 @@ func getAlgAndKeyFromJWK(j []byte) (cose.Algorithm, crypto.Signer, error) { return noAlg, nil, fmt.Errorf("unknown elliptic curve %v", crv) } case ed25519.PrivateKey: - alg = cose.AlgorithmEd25519 + alg = cose.AlgorithmEdDSA case *rsa.PrivateKey: alg = rsaJWKToAlg(k) if alg == noAlg { From 1823f9c038daca79f03d12325360f37b1af65cec Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 11 Sep 2023 15:37:55 +0100 Subject: [PATCH 008/110] Switch to v2 development There is already a v1 release published to pkg.dev. This was premature as a number of breaking changes to the API are still planned. The underlying spec is still in development with regular drafts being released. This code is going to change tracking the changes in the spec, as well as likely making non-spec changes to the API as they slowly mature. In order to stay compliant with golang [versioning workflow](https://go.dev/doc/modules/release-workflow), development is witching over to v2, so that v1 can remain stable. (note: the first full v2 release is expected to align with the final publication of the CoRIM spec, and so will not happen for some time. In the mean time, "release candidates" will be published to provided milestone references for dependents) Signed-off-by: Sergei Trofimov --- Makefile | 8 ++++---- README.md | 8 ++++++-- cocli/cmd/comidCreate.go | 2 +- cocli/cmd/comidCreate_test.go | 2 +- cocli/cmd/comidValidate.go | 2 +- cocli/cmd/common.go | 4 ++-- cocli/cmd/corimCreate.go | 6 +++--- cocli/cmd/corimDisplay.go | 4 ++-- cocli/cmd/corimExtract.go | 4 ++-- cocli/cmd/corimSign.go | 2 +- cocli/cmd/corimSubmit_test.go | 2 +- cocli/cmd/corimVerify.go | 2 +- cocli/cmd/cotsCreate.go | 2 +- cocli/cmd/test_vars.go | 2 +- cocli/main.go | 2 +- corim/cbor.go | 2 +- corim/entity.go | 2 +- corim/entity_test.go | 2 +- corim/meta.go | 2 +- corim/meta_test.go | 2 +- corim/signedcorim.go | 2 +- corim/unsignedcorim.go | 4 ++-- corim/unsignedcorim_test.go | 4 ++-- cots/cbor.go | 2 +- cots/cots.go | 2 +- cots/cots_test.go | 2 +- cots/env_group.go | 2 +- cots/env_group_test.go | 2 +- cots/example_test.go | 2 +- go.mod | 2 +- 30 files changed, 45 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index de492694..1511e322 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,10 @@ SHELL := /bin/bash GO111MODULE := on -GOPKG := github.com/veraison/corim/corim -GOPKG += github.com/veraison/corim/comid -GOPKG += github.com/veraison/corim/cots -GOPKG += github.com/veraison/corim/cocli/cmd +GOPKG := github.com/veraison/corim/v2/corim +GOPKG += github.com/veraison/corim/v2/comid +GOPKG += github.com/veraison/corim/v2/cots +GOPKG += github.com/veraison/corim/v2/cocli/cmd MOCKGEN := $(shell go env GOPATH)/bin/mockgen INTERFACES := cocli/cmd/isubmitter.go diff --git a/README.md b/README.md index 5e9f2a52..6f9f5685 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,13 @@ [![linters](https://github.com/veraison/corim/actions/workflows/linters.yml/badge.svg)](https://github.com/veraison/corim/actions/workflows/linters.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/veraison/corim.svg)](https://pkg.go.dev/github.com/veraison/corim) -The [`corim/corim`](corim) and [`corim/comid`](comid) packages provide a golang API for low-level manipulation of [Concise Reference Integrity Manifest (CoRIM)](https://datatracker.ietf.org/doc/draft-birkholz-rats-corim/) and Concise Module Identifier (CoMID) tags respectively. +The [`corim/v2/corim`](corim) and [`corim/v2/comid`](comid) packages provide a golang API for low-level manipulation of [Concise Reference Integrity Manifest (CoRIM)](https://datatracker.ietf.org/doc/draft-ietf-rats-corim/) and Concise Module Identifier (CoMID) tags respectively. -The [`corim/cocli`](cocli) package uses the API above (as well as the API from [`veraison/swid`](https://github.com/veraison/swid) package) to provide a user friendly command line interface for working with CoRIM, CoMID, CoSWID and CoTS. Specifically it allows creating, signing, verifying, displaying, uploading, and more. See [`cocli/README.md`](cocli/README.md) for further details. +> [!NOTE] +> These API are still in active development (as is the underlying CoRIM spec). +> They are **subject to change** in the future. + +The [`corim/v2/cocli`](cocli) package uses the API above (as well as the API from [`veraison/swid`](https://github.com/veraison/swid) package) to provide a user friendly command line interface for working with CoRIM, CoMID, CoSWID and CoTS. Specifically it allows creating, signing, verifying, displaying, uploading, and more. See [`cocli/README.md`](cocli/README.md) for further details. ## Developer tips diff --git a/cocli/cmd/comidCreate.go b/cocli/cmd/comidCreate.go index bc70c0cc..b28a4847 100644 --- a/cocli/cmd/comidCreate.go +++ b/cocli/cmd/comidCreate.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) var ( diff --git a/cocli/cmd/comidCreate_test.go b/cocli/cmd/comidCreate_test.go index b651ebd6..63ee4ce3 100644 --- a/cocli/cmd/comidCreate_test.go +++ b/cocli/cmd/comidCreate_test.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) func Test_ComidCreateCmd_unknown_argument(t *testing.T) { diff --git a/cocli/cmd/comidValidate.go b/cocli/cmd/comidValidate.go index 5fe7ff1f..f799796c 100644 --- a/cocli/cmd/comidValidate.go +++ b/cocli/cmd/comidValidate.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) var ( diff --git a/cocli/cmd/common.go b/cocli/cmd/common.go index 8ba508b0..be8493f0 100644 --- a/cocli/cmd/common.go +++ b/cocli/cmd/common.go @@ -10,8 +10,8 @@ import ( "strings" "github.com/spf13/afero" - "github.com/veraison/corim/comid" - "github.com/veraison/corim/cots" + "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/v2/cots" "github.com/veraison/swid" ) diff --git a/cocli/cmd/corimCreate.go b/cocli/cmd/corimCreate.go index d4206464..34dbb92b 100644 --- a/cocli/cmd/corimCreate.go +++ b/cocli/cmd/corimCreate.go @@ -9,9 +9,9 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/comid" - "github.com/veraison/corim/corim" - "github.com/veraison/corim/cots" + "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/v2/corim" + "github.com/veraison/corim/v2/cots" "github.com/veraison/swid" ) diff --git a/cocli/cmd/corimDisplay.go b/cocli/cmd/corimDisplay.go index dfc9dcf2..6650feb4 100644 --- a/cocli/cmd/corimDisplay.go +++ b/cocli/cmd/corimDisplay.go @@ -11,8 +11,8 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/corim" - "github.com/veraison/corim/cots" + "github.com/veraison/corim/v2/corim" + "github.com/veraison/corim/v2/cots" ) var ( diff --git a/cocli/cmd/corimExtract.go b/cocli/cmd/corimExtract.go index 80d39fd5..f88dbd8b 100644 --- a/cocli/cmd/corimExtract.go +++ b/cocli/cmd/corimExtract.go @@ -11,8 +11,8 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/corim" - "github.com/veraison/corim/cots" + "github.com/veraison/corim/v2/corim" + "github.com/veraison/corim/v2/cots" ) var ( diff --git a/cocli/cmd/corimSign.go b/cocli/cmd/corimSign.go index bf9bb8b2..67e52ae1 100644 --- a/cocli/cmd/corimSign.go +++ b/cocli/cmd/corimSign.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/corim" + "github.com/veraison/corim/v2/corim" cose "github.com/veraison/go-cose" ) diff --git a/cocli/cmd/corimSubmit_test.go b/cocli/cmd/corimSubmit_test.go index 5fa29d20..3d9ed4cd 100644 --- a/cocli/cmd/corimSubmit_test.go +++ b/cocli/cmd/corimSubmit_test.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - mock_deps "github.com/veraison/corim/cocli/cmd/mocks" + mock_deps "github.com/veraison/corim/v2/cocli/cmd/mocks" ) func Test_CorimSubmitCmd_bad_server_url(t *testing.T) { diff --git a/cocli/cmd/corimVerify.go b/cocli/cmd/corimVerify.go index 6c5043d3..24988890 100644 --- a/cocli/cmd/corimVerify.go +++ b/cocli/cmd/corimVerify.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/corim" + "github.com/veraison/corim/v2/corim" ) var ( diff --git a/cocli/cmd/cotsCreate.go b/cocli/cmd/cotsCreate.go index eb3c52dd..22686045 100644 --- a/cocli/cmd/cotsCreate.go +++ b/cocli/cmd/cotsCreate.go @@ -11,7 +11,7 @@ import ( "github.com/google/uuid" "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/cots" + "github.com/veraison/corim/v2/cots" ) var ( diff --git a/cocli/cmd/test_vars.go b/cocli/cmd/test_vars.go index e4f05190..7a452ba7 100644 --- a/cocli/cmd/test_vars.go +++ b/cocli/cmd/test_vars.go @@ -3,7 +3,7 @@ package cmd -import "github.com/veraison/corim/comid" +import "github.com/veraison/corim/v2/comid" var ( minimalCorimTemplate = []byte(`{ diff --git a/cocli/main.go b/cocli/main.go index c3fc2a3f..7af5ab14 100644 --- a/cocli/main.go +++ b/cocli/main.go @@ -4,7 +4,7 @@ package main import ( - "github.com/veraison/corim/cocli/cmd" + "github.com/veraison/corim/v2/cocli/cmd" ) func main() { diff --git a/corim/cbor.go b/corim/cbor.go index ec15e95f..3b59c6bc 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -7,7 +7,7 @@ import ( "reflect" cbor "github.com/fxamacker/cbor/v2" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) var ( diff --git a/corim/entity.go b/corim/entity.go index 49aa1468..da8d56fa 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -6,7 +6,7 @@ package corim import ( "fmt" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) // Entity stores an entity-map capable of CBOR and JSON serializations. diff --git a/corim/entity_test.go b/corim/entity_test.go index 457b3770..46c95bda 100644 --- a/corim/entity_test.go +++ b/corim/entity_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) func TestEntity_Valid_uninitialized(t *testing.T) { diff --git a/corim/meta.go b/corim/meta.go index 45f1ea49..25cc9d6f 100644 --- a/corim/meta.go +++ b/corim/meta.go @@ -9,7 +9,7 @@ import ( "fmt" "time" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) type Signer struct { diff --git a/corim/meta_test.go b/corim/meta_test.go index a4dd34cd..ae37c16f 100644 --- a/corim/meta_test.go +++ b/corim/meta_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) var ( diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 755462e1..7b5f27d8 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -45,7 +45,7 @@ func (o *SignedCorim) processHdrs() error { // TODO(tho) key id is apparently mandatory, which doesn't look right. // TODO(tho) Check with the CoRIM design team. - // See https://github.com/veraison/corim/issues/14 + // See https://github.com/veraison/corim/v2/issues/14 v, ok = hdr.Protected[HeaderLabelCorimMeta] if !ok { diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 68ca6786..3aeb0856 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -9,9 +9,9 @@ import ( "fmt" "time" - "github.com/veraison/corim/cots" + "github.com/veraison/corim/v2/cots" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" "github.com/veraison/eat" "github.com/veraison/swid" ) diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 3fc99166..61a15646 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -11,8 +11,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/corim/comid" - "github.com/veraison/corim/cots" + "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/v2/cots" "github.com/veraison/swid" ) diff --git a/cots/cbor.go b/cots/cbor.go index 6d69ecd1..1c4580a1 100644 --- a/cots/cbor.go +++ b/cots/cbor.go @@ -7,7 +7,7 @@ import ( "reflect" cbor "github.com/fxamacker/cbor/v2" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) var ( diff --git a/cots/cots.go b/cots/cots.go index 477ac88a..623557ba 100644 --- a/cots/cots.go +++ b/cots/cots.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" "github.com/veraison/swid" ) diff --git a/cots/cots_test.go b/cots/cots_test.go index 134f1d7d..cb0f2330 100644 --- a/cots/cots_test.go +++ b/cots/cots_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) func TestConciseTaStore_Valid_no_environment_groups(t *testing.T) { diff --git a/cots/env_group.go b/cots/env_group.go index 01d98efb..7dc07433 100644 --- a/cots/env_group.go +++ b/cots/env_group.go @@ -7,7 +7,7 @@ import ( "encoding/json" "fmt" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" ) // EnvironmentGroup is the top-level representation of the unsigned-corim-map with diff --git a/cots/env_group_test.go b/cots/env_group_test.go index 5cd41090..3a6a1495 100644 --- a/cots/env_group_test.go +++ b/cots/env_group_test.go @@ -6,7 +6,7 @@ package cots import ( "testing" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" "github.com/veraison/swid" "github.com/stretchr/testify/assert" diff --git a/cots/example_test.go b/cots/example_test.go index f94ecbd7..168c8962 100644 --- a/cots/example_test.go +++ b/cots/example_test.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/veraison/corim/comid" + "github.com/veraison/corim/v2/comid" "github.com/veraison/swid" ) diff --git a/go.mod b/go.mod index 3fe5919b..b09d2f00 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/veraison/corim +module github.com/veraison/corim/v2 go 1.16 From 58a92b0a73ea399fcaef47c50779f9a00b0f7e08 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 1 Sep 2023 16:50:17 +0100 Subject: [PATCH 009/110] comid: implement Crypto Keys Implement Crypto Keys as described by section 3.1.4.1.6 of draft-ietf-rats-corim-02: https://www.ietf.org/archive/id/draft-ietf-rats-corim-02.html#name-crypto-keys The replaces the equivalent VerifKey construct in the existing implementation. Crypo Keys are one of pre-defined types, allowing for stronger validation of the underlying value. As part of this change, the dependency on swid library is updated to the most current version, which changes the representation of digests to use ";" instead of ":" as the separator. Signed-off-by: Sergei Trofimov --- .github/workflows/ci-go-cover.yml | 2 +- .github/workflows/linters.yml | 4 +- comid/attestverifkey.go | 4 +- comid/attestverifkey_test.go | 16 +- comid/cbor.go | 7 + comid/cryptokey.go | 682 ++++++++++++++ comid/cryptokey_test.go | 401 ++++++++ comid/cryptokeys.go | 37 + comid/cryptokeys_test.go | 30 + comid/devidentitykey.go | 4 +- comid/devidentitykey_test.go | 15 +- comid/digests_test.go | 2 +- comid/example_psa_keys_test.go | 6 +- comid/example_test.go | 61 +- comid/test_vars.go | 158 +++- comid/verifkey.go | 50 - comid/verifkeys.go | 35 - corim/unsignedcorim_test.go | 7 +- cots/example_abbreviated_swid_tag_test.go | 2 +- go.mod | 41 +- go.sum | 1008 +-------------------- 21 files changed, 1416 insertions(+), 1156 deletions(-) create mode 100644 comid/cryptokey.go create mode 100644 comid/cryptokey_test.go create mode 100644 comid/cryptokeys.go create mode 100644 comid/cryptokeys_test.go delete mode 100644 comid/verifkey.go delete mode 100644 comid/verifkeys.go diff --git a/.github/workflows/ci-go-cover.yml b/.github/workflows/ci-go-cover.yml index dc3244fd..94b48854 100644 --- a/.github/workflows/ci-go-cover.yml +++ b/.github/workflows/ci-go-cover.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: "1.17.2" + go-version: "1.18" - name: Checkout code uses: actions/checkout@v2 - name: Install mockgen diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 96ac1328..665e7048 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: "1.17.2" + go-version: "1.18" - name: Checkout code uses: actions/checkout@v2 - name: Install golangci-lint @@ -19,7 +19,7 @@ jobs: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.2 - name: Install mockgen run: | - go install github.com/golang/mock/mockgen@v1.5.0 + go install github.com/golang/mock/mockgen@v1.5.0 - name: Run required linters in .golangci.yml plus hard-coded ones here run: make lint - name: Run optional linters (not required to pass) diff --git a/comid/attestverifkey.go b/comid/attestverifkey.go index 2529952a..b8e86a05 100644 --- a/comid/attestverifkey.go +++ b/comid/attestverifkey.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2023 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -14,7 +14,7 @@ import ( type AttestVerifKey struct { _ struct{} `cbor:",toarray"` Environment Environment `json:"environment"` - VerifKeys VerifKeys `json:"verification-keys"` + VerifKeys CryptoKeys `json:"verification-keys"` } func (o AttestVerifKey) Valid() error { diff --git a/comid/attestverifkey_test.go b/comid/attestverifkey_test.go index 3a7241f6..659a0abc 100644 --- a/comid/attestverifkey_test.go +++ b/comid/attestverifkey_test.go @@ -1,3 +1,6 @@ +// Copyright 2021-2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + package comid import ( @@ -7,26 +10,27 @@ import ( ) func TestAttestVerifKey_Valid_empty(t *testing.T) { + invalidKey := CryptoKey{TaggedPKIXBase64Key("")} tvs := []struct { env Environment - verifkey VerifKeys + verifkey CryptoKeys testerr string }{ { env: Environment{}, - verifkey: VerifKeys{}, + verifkey: CryptoKeys{}, testerr: "environment validation failed: environment must not be empty", }, { env: Environment{Instance: NewInstanceUEID(TestUEID)}, - verifkey: VerifKeys{}, - testerr: "verification keys validation failed: no verification key to validate", + verifkey: CryptoKeys{}, + testerr: "verification keys validation failed: no keys to validate", }, { env: Environment{Instance: NewInstanceUEID(TestUEID)}, - verifkey: VerifKeys{{Key: ""}}, - testerr: "verification keys validation failed: invalid verification key at index 0: verification key not set", + verifkey: CryptoKeys{&invalidKey}, + testerr: "verification keys validation failed: invalid key at index 0: key value not set", }, } for _, tv := range tvs { diff --git a/comid/cbor.go b/comid/cbor.go index fec19025..c44d518d 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -24,7 +24,14 @@ func comidTags() cbor.TagSet { //551: To Do see: https://github.com/veraison/corim/issues/32 552: TaggedSVN(0), 553: TaggedMinSVN(0), + 554: TaggedPKIXBase64Key(""), + 555: TaggedPKIXBase64Cert(""), + 556: TaggedPKIXBase64CertPath(""), + 557: TaggedThumbprint{}, + 558: TaggedCOSEKey{}, + 559: TaggedCertThumbprint{}, 560: TaggedRawValueBytes{}, + 561: TaggedCertPathThumbprint{}, // PSA profile tags 600: TaggedImplID{}, 601: TaggedPSARefValID{}, diff --git a/comid/cryptokey.go b/comid/cryptokey.go new file mode 100644 index 00000000..53d99df8 --- /dev/null +++ b/comid/cryptokey.go @@ -0,0 +1,682 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "bytes" + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + + "github.com/fxamacker/cbor/v2" + "github.com/veraison/go-cose" + "github.com/veraison/swid" +) + +const ( + // PKIXBase64KeyType indicates a PEM-encoded SubjectPublicKeyInfo. See + // https://www.rfc-editor.org/rfc/rfc7468#section-13 + PKIXBase64KeyType = "pkix-base64-key" + // PKIXBase64CertType indicates a PEM-encoded X.509 public key + // certificate. See https://www.rfc-editor.org/rfc/rfc7468#section-5 + PKIXBase64CertType = "pkix-base64-cert" + // PKIXBase64CertPathType indicates a X.509 certificate chain created + // by the concatenation of as many PEM encoded X.509 certificates as + // needed. The certificates MUST be concatenated in order so that each + // directly certifies the one preceding. + PKIXBase64CertPathType = "pkix-base64-cert-path" + // COSEKeyType represents a CBOR encoded COSE_Key or COSE_KeySet. See + // https://www.rfc-editor.org/rfc/rfc9052#section-7 + COSEKeyType = "cose-key" + // ThumbprintType represents a digest of a raw public key. The digest + // value may be used to find the public key if contained in a lookup + // table. + ThumbprintType = "thumbprint" + // CertThumbprintType represents a digest of a certificate. The digest + // value may be used to find the certificate if contained in a lookup + // table. + CertThumbprintType = "cert-thumbprint" + // CertPathThumbprintType represents a digest of a certification path. + // The digest value may be used to find the certificate path if + // contained in a lookup table. + CertPathThumbprintType = "cert-path-thumbprint" +) + +// CryptoKey is the struct implementing CoRIM crypto-key-type-choice. See +// https://www.ietf.org/archive/id/draft-ietf-rats-corim-02.html#name-crypto-keys +type CryptoKey struct { + Value ICryptoKeyValue +} + +// NewCryptoKey returns the pointer to a new CryptoKey of the specified type, +// constructed using the provided value k. The type of k depends on the +// specified crypto key type. For PKIX types, k must be a string. For COSE_Key, +// k must be a []byte. For thumbprint types, k must be a swid.HashEntry. +func NewCryptoKey(k any, typ string) (*CryptoKey, error) { + switch typ { + case PKIXBase64KeyType: + v, ok := k.(string) + if !ok { + return nil, fmt.Errorf("value must be a string; found %T", k) + } + return NewPKIXBase64Key(v) + case PKIXBase64CertType: + v, ok := k.(string) + if !ok { + return nil, fmt.Errorf("value must be a string; found %T", k) + } + return NewPKIXBase64Cert(v) + case PKIXBase64CertPathType: + v, ok := k.(string) + if !ok { + return nil, fmt.Errorf("value must be a string; found %T", k) + } + return NewPKIXBase64CertPath(v) + case COSEKeyType: + v, ok := k.([]byte) + if !ok { + return nil, fmt.Errorf("value must be a []byte; found %T", k) + } + return NewCOSEKey(v) + case ThumbprintType, CertThumbprintType, CertPathThumbprintType: + v, ok := k.(swid.HashEntry) + if !ok { + return nil, fmt.Errorf("value must be a swid.HashEntry; found %T", k) + } + switch typ { + case ThumbprintType: + return NewThumbprint(v) + case CertThumbprintType: + return NewCertThumbprint(v) + case CertPathThumbprintType: + return NewCertPathThumbprint(v) + default: + // Should never here because of the the outer case clause + panic(fmt.Sprintf("unexpected thumbprint type: %s", typ)) + } + default: + return nil, fmt.Errorf("unexpected CryptoKey type: %s", typ) + } +} + +// MustNewCryptoKey is the same as NewCryptoKey, but does not return an error, +// and panics if there is a problem. +func MustNewCryptoKey(k any, typ string) *CryptoKey { + key, err := NewCryptoKey(k, typ) + if err != nil { + panic(err) + } + + return key +} + +// String returns the string representation of the CryptoKey. +func (o CryptoKey) String() string { + return o.Value.String() +} + +// Valid returns an error if validation of the CryptoKey fails, or nil if it +// succeeds. +func (o CryptoKey) Valid() error { + return o.Value.Valid() +} + +// PublicKey returns a crypto.PublicKey constructed from the CryptoKey's +// underlying value. This returns an error if the CryptoKey is one of the +// thumbprint types. +func (o CryptoKey) PublicKey() (crypto.PublicKey, error) { + return o.Value.PublicKey() +} + +// MarshalJSON returns a []byte containing the JSON representation of the +// CryptoKey. +func (o CryptoKey) MarshalJSON() ([]byte, error) { + value := struct { + Type string `json:"type"` + Value string `json:"value"` + }{ + Value: o.Value.String(), + } + + switch o.Value.(type) { + case TaggedPKIXBase64Key: + value.Type = PKIXBase64KeyType + case TaggedPKIXBase64Cert: + value.Type = PKIXBase64CertType + case TaggedPKIXBase64CertPath: + value.Type = PKIXBase64CertPathType + case TaggedCOSEKey: + value.Type = COSEKeyType + case TaggedThumbprint: + value.Type = ThumbprintType + case TaggedCertThumbprint: + value.Type = CertThumbprintType + case TaggedCertPathThumbprint: + value.Type = CertPathThumbprintType + default: + return nil, fmt.Errorf("unexpected ICryptoKeyValue type: %T", o.Value) + } + + return json.Marshal(value) +} + +// UnmarshalJSON populates the CryptoKey from the JSON representation inside +// the provided []byte. +func (o *CryptoKey) UnmarshalJSON(b []byte) error { + var value struct { + Type string `json:"type"` + Value string `json:"value"` + } + + if err := json.Unmarshal(b, &value); err != nil { + return err + } + + if value.Type == "" { + return errors.New("key type not set") + } + + switch value.Type { + case PKIXBase64KeyType: + o.Value = TaggedPKIXBase64Key(value.Value) + case PKIXBase64CertType: + o.Value = TaggedPKIXBase64Cert(value.Value) + case PKIXBase64CertPathType: + o.Value = TaggedPKIXBase64CertPath(value.Value) + case COSEKeyType: + data, err := base64.StdEncoding.DecodeString(value.Value) + if err != nil { + return fmt.Errorf("base64 decode error: %w", err) + } + o.Value = TaggedCOSEKey(data) + case ThumbprintType, CertThumbprintType, CertPathThumbprintType: + he, err := swid.ParseHashEntry(value.Value) + if err != nil { + return fmt.Errorf("swid.HashEntry decode error: %w", err) + } + switch value.Type { + case ThumbprintType: + o.Value = TaggedThumbprint{digest{he}} + case CertThumbprintType: + o.Value = TaggedCertThumbprint{digest{he}} + case CertPathThumbprintType: + o.Value = TaggedCertPathThumbprint{digest{he}} + } + default: + return fmt.Errorf("unexpected ICryptoKeyValue type: %q", value.Type) + } + + return o.Valid() +} + +// MarshalCBOR returns a []byte containing the CBOR representation of the +// CryptoKey. +func (o CryptoKey) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.Value) +} + +// UnmarshalCBOR populates the CryptoKey from the CBOR representation inside +// the provided []byte. +func (o *CryptoKey) UnmarshalCBOR(b []byte) error { + return dm.Unmarshal(b, &o.Value) +} + +// ICryptoKeyValue is the interface implemented by the concrete CryptoKey value +// types. +type ICryptoKeyValue interface { + // String returns the string representation of the ICryptoKeyValue. + String() string + // Valid returns an error if validation of the ICryptoKeyValue fails, + // or nil if it succeeds. + Valid() error + // PublicKey returns a crypto.PublicKey constructed from the + // ICryptoKeyValue's underlying value. This returns an error if the + // ICryptoKeyValue is one of the thumbprint types. + PublicKey() (crypto.PublicKey, error) +} + +// TaggedPKIXBase64Key is a PEM-encoded SubjectPublicKeyInfo. See +// https://www.rfc-editor.org/rfc/rfc7468#section-13 +type TaggedPKIXBase64Key string + +func NewPKIXBase64Key(s string) (*CryptoKey, error) { + key := TaggedPKIXBase64Key(s) + if err := key.Valid(); err != nil { + return nil, err + } + return &CryptoKey{key}, nil +} + +func MustNewPKIXBase64Key(s string) *CryptoKey { + key, err := NewPKIXBase64Key(s) + if err != nil { + panic(err) + } + return key +} + +func (o TaggedPKIXBase64Key) String() string { + return string(o) +} + +func (o TaggedPKIXBase64Key) Valid() error { + _, err := o.PublicKey() + return err +} + +func (o TaggedPKIXBase64Key) PublicKey() (crypto.PublicKey, error) { + if string(o) == "" { + return nil, errors.New("key value not set") + } + + block, rest := pem.Decode([]byte(o)) + if block == nil { + return nil, errors.New("could not decode PEM block") + } + + if len(rest) != 0 { + return nil, errors.New("trailing data found after PEM block") + } + + if block.Type != "PUBLIC KEY" { + return nil, fmt.Errorf( + "unexpected PEM block type: %q, expected \"PUBLIC KEY\"", + block.Type, + ) + } + + key, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to parse public key: %w", err) + } + + return key, nil +} + +// TaggedPKIXBase64Cert is a PEM-encoded X.509 public key +// certificate. See https://www.rfc-editor.org/rfc/rfc7468#section-5 +type TaggedPKIXBase64Cert string + +func NewPKIXBase64Cert(s string) (*CryptoKey, error) { + cert := TaggedPKIXBase64Cert(s) + if err := cert.Valid(); err != nil { + return nil, err + } + return &CryptoKey{cert}, nil +} + +func MustNewPKIXBase64Cert(s string) *CryptoKey { + cert, err := NewPKIXBase64Cert(s) + if err != nil { + panic(err) + } + return cert +} + +func (o TaggedPKIXBase64Cert) String() string { + return string(o) +} + +func (o TaggedPKIXBase64Cert) Valid() error { + _, err := o.cert() + return err +} + +func (o TaggedPKIXBase64Cert) PublicKey() (crypto.PublicKey, error) { + cert, err := o.cert() + if err != nil { + return nil, err + } + + if cert.PublicKey == nil { + return nil, errors.New("cert does not contain a crypto.PublicKey") + } + + return cert.PublicKey, nil +} + +func (o TaggedPKIXBase64Cert) cert() (*x509.Certificate, error) { + if string(o) == "" { + return nil, errors.New("cert value not set") + } + + block, rest := pem.Decode([]byte(o)) + if block == nil { + return nil, errors.New("could not decode PEM block") + } + + if len(rest) != 0 { + return nil, errors.New("trailing data found after PEM block") + } + + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf( + "unexpected PEM block type: %q, expected \"CERTIFICATE\"", + block.Type, + ) + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("could not parse x509 cert: %w", err) + } + + return cert, nil +} + +// TaggedPKIXBase64CertPath is a X.509 certificate chain created +// by the concatenation of as many PEM encoded X.509 certificates as +// needed. The certificates MUST be concatenated in order so that each +// directly certifies the one preceding. +type TaggedPKIXBase64CertPath string + +func NewPKIXBase64CertPath(s string) (*CryptoKey, error) { + cert := TaggedPKIXBase64CertPath(s) + + if err := cert.Valid(); err != nil { + return nil, err + } + + return &CryptoKey{cert}, nil +} + +func MustNewPKIXBase64CertPath(s string) *CryptoKey { + cert, err := NewPKIXBase64CertPath(s) + + if err != nil { + panic(err) + } + + return cert +} + +func (o TaggedPKIXBase64CertPath) String() string { + return string(o) +} + +func (o TaggedPKIXBase64CertPath) Valid() error { + _, err := o.certPath() + return err +} + +func (o TaggedPKIXBase64CertPath) PublicKey() (crypto.PublicKey, error) { + certs, err := o.certPath() + if err != nil { + return nil, err + } + + if len(certs) == 0 { + return nil, errors.New("empty cert path") + } + + if certs[0].PublicKey == nil { + return nil, errors.New("leaf cert does not contain a crypto.PublicKey") + } + + return certs[0].PublicKey, nil +} + +func (o TaggedPKIXBase64CertPath) certPath() ([]*x509.Certificate, error) { + if string(o) == "" { + return nil, errors.New("cert value not set") + } + + var certs []*x509.Certificate + var block *pem.Block + var rest []byte + rest = []byte(o) + i := 0 + for { + if len(rest) == 0 { + break + } + + block, rest = pem.Decode(rest) + if block == nil { + return nil, fmt.Errorf("could not decode PEM block %d", i) + } + + if block.Type != "CERTIFICATE" { + return nil, fmt.Errorf( + "unexpected type for PEM block %d: %q, expected \"CERTIFICATE\"", + i, block.Type, + ) + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf( + "could not parse x509 cert in PEM block %d: %w", + i, err, + ) + } + + certs = append(certs, cert) + + i++ + } + + return certs, nil +} + +// TaggedCOSEKey is a CBOR encoded COSE_Key or COSE_KeySet. See +// https://www.rfc-editor.org/rfc/rfc9052#section-7 +type TaggedCOSEKey []byte + +func NewCOSEKey(b []byte) (*CryptoKey, error) { + key := TaggedCOSEKey(b) + + if err := key.Valid(); err != nil { + return nil, err + } + + return &CryptoKey{key}, nil +} + +func MustNewCOSEKey(b []byte) *CryptoKey { + key, err := NewCOSEKey(b) + + if err != nil { + panic(err) + } + + return key +} + +func (o TaggedCOSEKey) String() string { + return base64.StdEncoding.EncodeToString(o) +} + +func (o TaggedCOSEKey) Valid() error { + if len(o) == 0 { + return errors.New("empty COSE_Key bytes") + } + + var err error + + // CBOR Major type 4 == array == COSE_KeySet. Key sets are currently + // not supported by go-cose library. + if ((o[0] & 0xe0) >> 5) == 4 { + _, err = o.coseKeySet() + } else { + _, err = o.coseKey() + } + return err +} + +func (o TaggedCOSEKey) PublicKey() (crypto.PublicKey, error) { + if len(o) == 0 { + return nil, errors.New("empty COSE_Key value") + } + + // CBOR Major type 4 == array == COSE_KeySet. Key sets are currently + // not supported by go-cose library. + if ((o[0] & 0xe0) >> 5) == 4 { + keySet, err := o.coseKeySet() + if err != nil { + return nil, err + } + + if len(keySet) == 0 { + return nil, errors.New("empty COSE_KeySet") + } else if len(keySet) > 1 { + return nil, errors.New("COSE_KeySet contains more than one key") + } + + return keySet[0].PublicKey() + } + + coseKey, err := o.coseKey() + if err != nil { + return nil, err + } + + return coseKey.PublicKey() +} + +func (o TaggedCOSEKey) MarshalCBOR() ([]byte, error) { + var buf bytes.Buffer + + // encodeMarshalerType in github.com/fxamacker/cbor/v2 does not look up + // assocated Tags, so we have to write them ourselves. + if _, err := buf.Write([]byte{0xd9, 0x02, 0x2e}); err != nil { // tag 558 + return nil, err + } + + if _, err := buf.Write(o); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (o *TaggedCOSEKey) UnmarshalCBOR(b []byte) error { + // the first 3 bytes are the tag + if !bytes.Equal([]byte{0xd9, 0x02, 0x2e}, b[:3]) { + return errors.New("did not see CBOR tag 588 at the beginning of COSE_Key structure") + } + + *o = b[3:] + return nil +} + +func (o TaggedCOSEKey) coseKey() (*cose.Key, error) { + coseKey := new(cose.Key) + + if err := coseKey.UnmarshalCBOR(o); err != nil { + return nil, err + } + + return coseKey, nil +} + +func (o TaggedCOSEKey) coseKeySet() ([]*cose.Key, error) { + var keySet []*cose.Key + + if err := cbor.Unmarshal(o, &keySet); err != nil { + return nil, err + } + + return keySet, nil +} + +type digest struct { + swid.HashEntry +} + +func (o digest) String() string { + return o.HashEntry.String() +} + +func (o digest) Valid() error { + return swid.ValidHashEntry(o.HashAlgID, o.HashValue) +} + +func (o digest) PublicKey() (crypto.PublicKey, error) { + return nil, errors.New("cannot get PublicKey from a digest") +} + +// ThumbprintTypeTaggedThumbprint represents a digest of a raw public key. The +// digest value may be used to find the public key if contained in a lookup +// table. +type TaggedThumbprint struct { + digest +} + +func NewThumbprint(he swid.HashEntry) (*CryptoKey, error) { + key := &CryptoKey{TaggedThumbprint{digest{he}}} + + if err := key.Valid(); err != nil { + return nil, err + } + + return key, nil +} + +func MustNewThumbprint(he swid.HashEntry) *CryptoKey { + key, err := NewThumbprint(he) + + if err != nil { + panic(err) + } + + return key +} + +// TaggedCertThumbprint represents a digest of a certificate. The digest value +// may be used to find the certificate if contained in a lookup table. +type TaggedCertThumbprint struct { + digest +} + +func NewCertThumbprint(he swid.HashEntry) (*CryptoKey, error) { + key := &CryptoKey{TaggedCertThumbprint{digest{he}}} + + if err := key.Valid(); err != nil { + return nil, err + } + + return key, nil +} + +func MustNewCertThumbprint(he swid.HashEntry) *CryptoKey { + key, err := NewCertThumbprint(he) + + if err != nil { + panic(err) + } + + return key +} + +// TaggedCertPathThumbprint represents a digest of a certification path. The +// digest value may be used to find the certificate path if contained in a +// lookup table. +type TaggedCertPathThumbprint struct { + digest +} + +func NewCertPathThumbprint(he swid.HashEntry) (*CryptoKey, error) { + key := &CryptoKey{TaggedCertPathThumbprint{digest{he}}} + + if err := key.Valid(); err != nil { + return nil, err + } + + return key, nil +} + +func MustNewCertPathThumbprint(he swid.HashEntry) *CryptoKey { + key, err := NewCertPathThumbprint(he) + + if err != nil { + panic(err) + } + + return key +} diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go new file mode 100644 index 00000000..e1a3b4f8 --- /dev/null +++ b/comid/cryptokey_test.go @@ -0,0 +1,401 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/swid" +) + +func Test_CryptoKey_NewPKIXBase64Key(t *testing.T) { + key, err := NewPKIXBase64Key(TestECPubKey) + require.NoError(t, err) + assert.Equal(t, TestECPubKey, key.String()) + pub, err := key.PublicKey() + assert.NoError(t, err) + assert.NotNil(t, pub) + + _, err = NewPKIXBase64Key("") + assert.EqualError(t, err, "key value not set") + + noPem := "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8BlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==" + _, err = NewPKIXBase64Key(noPem) + assert.EqualError(t, err, "could not decode PEM block") + + badBlock := "-----BEGIN CERTIFICATE-----\nDEADBEEF\n-----END CERTIFICATE-----" + _, err = NewPKIXBase64Key(badBlock) + assert.Contains(t, err.Error(), "unexpected PEM block type") + + badKey := "-----BEGIN PUBLIC KEY-----\nDEADBEEF\n-----END PUBLIC KEY-----" + _, err = NewPKIXBase64Key(badKey) + assert.Contains(t, err.Error(), "unable to parse public key") + + key = MustNewPKIXBase64Key(TestECPubKey) + assert.Equal(t, TestECPubKey, key.String()) + + assert.Panics(t, func() { + MustNewPKIXBase64Key(badBlock) + }) +} + +func Test_CryptoKey_NewPKIXBase64Cert(t *testing.T) { + cert, err := NewPKIXBase64Cert(TestCert) + require.NoError(t, err) + assert.Equal(t, TestCert, cert.String()) + pub, err := cert.PublicKey() + assert.NoError(t, err) + assert.NotNil(t, pub) + + _, err = NewPKIXBase64Cert("") + assert.EqualError(t, err, "cert value not set") + + noPem := "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8BlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==" + _, err = NewPKIXBase64Cert(noPem) + assert.EqualError(t, err, "could not decode PEM block") + + badBlock := "-----BEGIN PUBLIC KEY-----\nDEADBEEF\n-----END PUBLIC KEY-----" + _, err = NewPKIXBase64Cert(badBlock) + assert.Contains(t, err.Error(), "unexpected PEM block type") + + badCert := "-----BEGIN CERTIFICATE-----\nDEADBEEF\n-----END CERTIFICATE-----" + _, err = NewPKIXBase64Cert(badCert) + assert.Contains(t, err.Error(), "could not parse x509 cert") + + cert = MustNewPKIXBase64Cert(TestCert) + assert.Equal(t, TestCert, cert.String()) + + assert.Panics(t, func() { + MustNewPKIXBase64Cert(badBlock) + }) +} + +func Test_CryptoKey_NewPKIXBase64CertPath(t *testing.T) { + certs, err := NewPKIXBase64CertPath(TestCertPath) + assert.NoError(t, err) + assert.Equal(t, TestCertPath, certs.String()) + pub, err := certs.PublicKey() + assert.NoError(t, err) + assert.NotNil(t, pub) + + _, err = NewPKIXBase64CertPath("") + assert.EqualError(t, err, "cert value not set") + + noPem := "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8BlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==" + _, err = NewPKIXBase64CertPath(noPem) + assert.EqualError(t, err, "could not decode PEM block 0") + + badBlock := "-----BEGIN PUBLIC KEY-----\nDEADBEEF\n-----END PUBLIC KEY-----" + _, err = NewPKIXBase64CertPath(badBlock) + assert.Contains(t, err.Error(), "unexpected type for PEM block 0") + + badCert := "-----BEGIN CERTIFICATE-----\nDEADBEEF\n-----END CERTIFICATE-----" + _, err = NewPKIXBase64CertPath(badCert) + assert.Contains(t, err.Error(), "could not parse x509 cert in PEM block 0") + + certs = MustNewPKIXBase64CertPath(TestCertPath) + assert.Equal(t, TestCertPath, certs.String()) + + assert.Panics(t, func() { + MustNewPKIXBase64CertPath(badBlock) + }) +} + +func Test_CryptoKey_NewCOSEKey(t *testing.T) { + key, err := NewCOSEKey(TestCOSEKey) + require.NoError(t, err) + assert.Equal(t, base64.StdEncoding.EncodeToString(TestCOSEKey), key.String()) + pub, err := key.PublicKey() + assert.NoError(t, err) + assert.NotNil(t, pub) + + _, err = NewCOSEKey([]byte{}) + assert.EqualError(t, err, "empty COSE_Key bytes") + + _, err = NewCOSEKey([]byte("DEADBEEF")) + assert.Contains(t, err.Error(), "cbor: cannot unmarshal") + + badKey := []byte{ // taken from go-cose unit tests + 0xa2, // map(2) + 0x01, 0x01, // kty: OKP + 0x03, 0x41, 0x01, // alg: bstr(1) + } + _, err = NewCOSEKey(badKey) + assert.Contains(t, err.Error(), "alg: invalid type") + + keySet, err := NewCOSEKey(TestCOSEKeySetOne) + require.NoError(t, err) + pub, err = keySet.PublicKey() + assert.NoError(t, err) + assert.NotNil(t, pub) + + keySet, err = NewCOSEKey(TestCOSEKeySetMulti) + require.NoError(t, err) + _, err = keySet.PublicKey() + assert.Contains(t, err.Error(), "COSE_KeySet contains more than one key") + + key = MustNewCOSEKey(TestCOSEKey) + assert.Equal(t, base64.StdEncoding.EncodeToString(TestCOSEKey), key.String()) + + assert.Panics(t, func() { + MustNewCOSEKey(badKey) + }) +} + +func Test_CryptoKey_NewThumbprint(t *testing.T) { + type newKeyFunc func(swid.HashEntry) (*CryptoKey, error) + + for _, newFunc := range []newKeyFunc{ + NewThumbprint, + NewCertThumbprint, + NewCertPathThumbprint, + } { + key, err := newFunc(TestThumbprint) + require.NoError(t, err) + assert.Equal(t, TestThumbprint.String(), key.String()) + _, err = key.PublicKey() + assert.EqualError(t, err, "cannot get PublicKey from a digest") + + badAlg := swid.HashEntry{ + HashAlgID: 99, + HashValue: MustHexDecode(nil, `deadbeef`), + } + _, err = newFunc(badAlg) + assert.Contains(t, err.Error(), "unknown hash algorithm 99") + + badHash := swid.HashEntry{ + HashAlgID: 1, + HashValue: MustHexDecode(nil, `deadbeef`), + } + _, err = newFunc(badHash) + assert.Contains(t, err.Error(), "length mismatch for hash algorithm") + } + + type mustNewKeyFunc func(swid.HashEntry) *CryptoKey + + for _, mustNewFunc := range []mustNewKeyFunc{ + MustNewThumbprint, + MustNewCertThumbprint, + MustNewCertPathThumbprint, + } { + key := mustNewFunc(TestThumbprint) + assert.Equal(t, TestThumbprint.String(), key.String()) + + assert.Panics(t, func() { + mustNewFunc(swid.HashEntry{}) + }) + } +} + +func Test_CryptoKey_JSON_roundtrip(t *testing.T) { + for _, tv := range []struct { + Type string + In any + Out string + }{ + { + Type: PKIXBase64KeyType, + In: TestECPubKey, + Out: TestECPubKey, + }, + { + Type: PKIXBase64CertType, + In: TestCert, + Out: TestCert, + }, + { + Type: PKIXBase64CertPathType, + In: TestCertPath, + Out: TestCertPath, + }, + { + Type: COSEKeyType, + In: TestCOSEKey, + Out: base64.StdEncoding.EncodeToString(TestCOSEKey), + }, + { + Type: ThumbprintType, + In: TestThumbprint, + Out: fmt.Sprintf("%s;%s", + TestThumbprint.AlgIDToString(), + base64.StdEncoding.EncodeToString(TestThumbprint.HashValue), + ), + }, + { + Type: CertThumbprintType, + In: TestThumbprint, + Out: fmt.Sprintf("%s;%s", + TestThumbprint.AlgIDToString(), + base64.StdEncoding.EncodeToString(TestThumbprint.HashValue), + ), + }, + { + Type: CertPathThumbprintType, + In: TestThumbprint, + Out: fmt.Sprintf("%s;%s", + TestThumbprint.AlgIDToString(), + base64.StdEncoding.EncodeToString(TestThumbprint.HashValue), + ), + }, + } { + key := MustNewCryptoKey(tv.In, tv.Type) + data, err := json.Marshal(key) + require.NoError(t, err) + + expected := fmt.Sprintf(`{"type": %q, "value": %q}`, tv.Type, tv.Out) + assert.JSONEq(t, expected, string(data)) + + var key2 CryptoKey + err = json.Unmarshal(data, &key2) + require.NoError(t, err) + assert.Equal(t, *key, key2) + } +} + +func Test_CryptoKey_UnmarshalJSON_negative(t *testing.T) { + var key CryptoKey + + for _, tv := range []struct { + Val string + ErrMsg string + }{ + { + Val: `@@`, + ErrMsg: "invalid character", + }, + { + Val: `{"value":"deadbeef"}`, + ErrMsg: "key type not set", + }, + { + Val: `{"type": "cose-key", "value":";;;"}`, + ErrMsg: "base64 decode error", + }, + { + Val: `{"type": "thumbprint", "value":"deadbeef"}`, + ErrMsg: "swid.HashEntry decode error", + }, + { + Val: `{"type": "random-key", "value":"deadbeef"}`, + ErrMsg: "unexpected ICryptoKeyValue type", + }, + } { + err := key.UnmarshalJSON([]byte(tv.Val)) + assert.ErrorContains(t, err, tv.ErrMsg) + } +} + +func Test_CryptoKey_CBOR_roundtrip(t *testing.T) { + for _, tv := range []struct { + Type string + In any + Out string + }{ + { + Type: PKIXBase64KeyType, + In: TestECPubKey, + Out: "d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d", + }, + { + Type: PKIXBase64CertType, + In: TestCert, + Out: "d9022b7902c82d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949423454434341596567417749424167495547687241394d337951494671636b413276366e5165776d4633304977436759494b6f5a497a6a3045417749770a5254454c4d416b474131554542684d4351565578457a415242674e564241674d436c4e766257557455335268644755784954416642674e5642416f4d47456c750a64475679626d5630494664705a47647064484d6755485235494578305a444167467730794d7a41354d4451784d5441784e446861474138794d4455784d4445780a4f5445784d4445304f466f775254454c4d416b474131554542684d4351565578457a415242674e564241674d436c4e76625755745533526864475578495441660a42674e5642416f4d47456c7564475679626d5630494664705a47647064484d6755485235494578305a44425a4d424d4742797147534d343941674547434371470a534d3439417745484130494142467451623668667636387641566d75325244464e63574742784550415a53302b4442527a6d6a7669416b37534c367848684b610a2f37483077442b7568396f516833572b527846797255537148656b433344697a774e576a557a42524d423047413155644467515742425157704e5062366557440a534d2f2b6a7770627a6f4f33694867344c54416642674e5648534d454744415767425157704e506236655744534d2f2b6a7770627a6f4f33694867344c5441500a42674e5648524d4241663845425441444151482f4d416f4743437147534d343942414d43413067414d455543494161794e49463065434a445a6d637271526a480a663968384778654944556e4c716c646549764e66612b39534169454139554c4254506a6e545568596c653232364f416a67327364686b587462334d75304530460a6e75556d7349513d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d", + }, + { + Type: PKIXBase64CertPathType, + In: TestCertPath, + Out: "d9022c7919272d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a4343416979674177494241674955497065567756684e2f71594c67744e4a6c775a484a6a2b49542f7777425159444b3256774d444d784d5441760a42674e56424155544b4464684d445a6c5a5755304d5749334f446c6d4e4467324d3251344e6d49344e7a6334596a46684d6a417859545a6d5a57526b4e5459770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b4449790a4f5463354e574d784e5467305a475a6c59545977596a67795a444d304f546b334d4459304e7a49324d3259344f44526d5a6d4d774b6a414642674d725a5841440a495141565569377856796e4d3835554a366c77566f6d767053654f494236584362766b6f4649667653755a3752714f43415534776767464b4d423847413155640a497751594d42614146486f4737755162654a394959396872683369786f67476d2f7431574d4230474131556444675157424251696c3558425745332b706775430a30306d58426b636d503468502f44414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f77534231444342306142434245414d7476656f414a783476335175394b386749304a326b70376967427634566439420a48454d746c665a4f5164793679444f6c6350725147596a537a7776416a4668384441343662712b78476a33314e46557936706b686f304945514e436b6c2f4b660a6245534c5a364f684563644f6e417a69533567783554714a6d463232794b436a49764c524956784e49685a4e32456e4d41746d3464703145477550424c5572410a747a58686c7a755a754b31785636536b516752415365536f486d6e4e674c686e6e454b54574b7a634c326a7a506a4f41465551544e52792b694f67686c7549330a66696347364e4237634d624c415a6b665631326c696853562b2f37694b33544a3062554e6a51675770615944436745424d4155474179746c63414e42414875360a447475504e4f7572634158632b3431515932336859384b526b42434b434537706873694977526662784b4d4c6c6446474e354f79745166524f5161576f4163760a4957547156394a527a47516147596e6c4c77453d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a434341697967417749424167495545626165377531635037732b472f434462324e75366e505264596b77425159444b3256774d444d784d5441760a42674e56424155544b4449794f5463354e574d784e5467305a475a6c59545977596a67795a444d304f546b334d4459304e7a49324d3259344f44526d5a6d4d770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b4445780a596a59355a57566c5a575131597a4e6d596d497a5a5446695a6a41344d7a5a6d4e6a4d325a5756684e7a4e6b4d5463314f446b774b6a414642674d725a5841440a4951447a676b54523775766f50394e427a5345423967752f6c70642b4e4c33384f5659516c30666569574b582b614f43415534776767464b4d423847413155640a497751594d42614146434b586c6346595466366d43344c54535a63475279592f69452f384d423047413155644467515742425152747037753756772f757a34620a38494e7659323771633946316954414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f775342314443423061424342454262734b67636176452b75793141786b49646c376c4e39696648793348452b4c69750a386c4d453237434d59396b557479772f6c657331483876706d537978684f3461545767777577516137596e39486f4777654548736f304945514862383730484e0a3162556e396e46696831315342416a396c6f6270754a354772492f6d2b673648776d6f517a35556c79306f584d4e6e78454d4137664c327a613031796e4770490a2f757a383272554932764c57536c476b5167524133584667496f56496d6f736441677675504856616f6276334a476a476c332b41444f543163366454366451450a646e4f62524e7564593871687a547666455752346553364f4a746679724f65527958656b324f564a68365944436745424d4155474179746c63414e42414a496a0a7946717764725a435375596d43342b5a555563414e4b514b41314b63524669496c4b63672f7070774b56796b5058624168736e365343567157474137763743650a4c6935684f72482f566c6a41514163645967633d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a43434169796741774942416749555955584d636656334d756370756e6c313933777558784242722f6777425159444b3256774d444d784d5441760a42674e56424155544b444578596a59355a57566c5a575131597a4e6d596d497a5a5446695a6a41344d7a5a6d4e6a4d325a5756684e7a4e6b4d5463314f446b770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b4459780a4e44566a597a63785a6a55334e7a4d795a5463794f574a684e7a6b334e5759334e324d795a54566d4d5441304d57466d5a6a67774b6a414642674d725a5841440a4951424a6f3950677665486a30616876384d6b574851554753785a2f77535464614e4e5a6264425a4e61314c30614f43415534776767464b4d423847413155640a497751594d426141464247326e75377458442b37506876776732396a6275707a3058574a4d423047413155644467515742425268526378783958637935796d360a6558583366433566454547762b44414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f775342314443423061424342454132616e736566305362524e386a37367735687a57352f54435846497351634552730a62534b51594e6e71756731726a4543506e686533412f385a3657477861444b316568452b6e72637643394252677257705536374a6f30494551495a795243484b0a394855692f387936563950305a754e45766d64704564496d51303952552f6c4e507358587879763056456d693657447334654679706d4252394c5658425875640a7243647575767953367442577353366b516752416257525443625872642f716c4c504949383549504238705a3975582b586749484934735348662b33463673650a68412f38307a55427a5369364f7a6330442b496259594259786472585a456b6e386955575364516f6b4b5944436745424d4155474179746c63414e42414b6c4a0a2f335659616c5a6d3958624547544b725652616f43566f55785156483375644d726b39796f716a466f77433465336b6453426c474766386d59454937787673410a6172316b6632624758542f634565464749774d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a4343416979674177494241674955582b69765048544f6d76566b744d6e514759516a754e6c6b2f445577425159444b3256774d444d784d5441760a42674e56424155544b4459784e44566a597a63785a6a55334e7a4d795a5463794f574a684e7a6b334e5759334e324d795a54566d4d5441304d57466d5a6a67770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b44566d0a5a5468685a6a4e6a4e7a526a5a546c685a6a55324e474930597a6c6b4d4445354f4451794d3249345a446b324e475a6a4d7a55774b6a414642674d725a5841440a495143367533626c77453442317864504d65554a50363537502f6d376953742b486572677647626b6b53784d72714f43415534776767464b4d423847413155640a497751594d426141464746467a484831647a4c6e4b627035646664384c6c385151612f344d423047413155644467515742425266364b3838644d3661395753300a7964415a68434f34325754384e54414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f7753423144434230614243424543347a326a754a4978356a4437783649754d4e55693754556f6d577843516639516e0a434a39316f7a586b30764a396e4a4f33526476654a7662765a686f5066445149593854695a7038554b447834652b7a573063486b6f304945514f68704d4a36470a45584c5a67487452416d38316f5858414345462b6e6576324d437636434f6875527446797047394233666f526d32726e46556261565a7330704c66424d4738730a7353524a526361775843696d57344f6b5167524132374667783741343231327170714c61786150643974492b7a70664b57724c59634c7832302b444c6663716e0a4249497055434e3330537541753731736534782f696c634b7561574f4f307144673334534a45774679715944436745424d4155474179746c63414e42414374540a3558727836353971476e79776d6c4b48646c484f364264376650626f797a794951686f4574464e75694433576a44672f56777a38634e43556b552b74684737660a432b575a68637041636b446c6461692b5041633d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a434341697967417749424167495558583044564f796c6745697037727a7679614e4665676344545a4977425159444b3256774d444d784d5441760a42674e56424155544b44566d5a5468685a6a4e6a4e7a526a5a546c685a6a55324e474930597a6c6b4d4445354f4451794d3249345a446b324e475a6a4d7a55770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b44566b0a4e3251774d7a55305a574e684e5467774e4468684f57566c596d4e6c5a6d4d3559544d304e5464684d4463774d7a526b4f5449774b6a414642674d725a5841440a495143696143326748684d4f31706265516255674c48685367464250442f7a584e414777414873573237322b63364f43415534776767464b4d423847413155640a497751594d42614146462f6f727a78307a7072315a4c544a30426d4549376a5a5a5077314d42304741315564446751574242526466514e55374b5741534b6e750a764f2f4a6f30563642774e4e6b6a414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f7753423144434230614243424542595877532f6d7272582b44344d717a4d384a546d49484339584871734a664f47630a623266714259505830555172694c44526c316170484e32327131452b4665614c4857424532755864613151366c596b51416148696f30494551474b483845414e0a4d7631504d4d62577364645a657732472f44522b4139746253693748363830794253653943652b6774616242617251444870673942384c65626d6f50706458740a4154762b6f537a7a6b2b5a7565564b6b516752417a74625532517a614a62634735747745596a59416746757443626e67706732742f32657a3751544e6e344e6d0a723934704f4178384c4970753643662f577a6376642f346b4c4f76577853622f62754d716247767273715944436745424d4155474179746c63414e42414d64780a66466e6b35327275346656354a3167734642746c6879356d4662516e49526947484c78424661544a6b39692b69784f35714662526a71763748512f6a475573490a7372554a513465324a456e534e616b4e6367453d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a434341697967417749424167495541316f576f57507756644335474f3362516f4433726f446531536f77425159444b3256774d444d784d5441760a42674e56424155544b44566b4e3251774d7a55305a574e684e5467774e4468684f57566c596d4e6c5a6d4d3559544d304e5464684d4463774d7a526b4f5449770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b44417a0a4e5745784e6d45784e6a4e6d4d4455315a4442694f5445345a57526b596a51794f44426d4e32466c4f44426b5a5751314d6d45774b6a414642674d725a5841440a495144534e45593167624c4d4e414f432b33656f6b2b5279513666684e384632336f32647836315162734d3054714f43415534776767464b4d423847413155640a497751594d4261414646313941315473705942497165363837386d6a52586f48413032534d42304741315564446751574242514457686168592f4256304c6b590a3764744367506575674e37564b6a414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f7753423144434230614243424541396d5070416d572b49454f58584f4267537933727935334935363244394f5a485a0a2b4447312f4d396d577869556b524131556369714d704767366e6779717033384a354f7055497546736f53564471465650796a786f3049455149472f376831370a416d3737474c6d51316e534d425a6a74724a2b46726d5754635a6a784a3963583043504a7175377775674c3554636a31493863396e424e71736f6b46783870450a74526f71697a377274365a353244326b516752415a7276714664796a347256636a74566b4a624d6c702f386a6d664765614b682f5247363457724b32754e6b390a79684b4f706b695152307035557354616d2b586445767172786a4c61343373723064692f704b45625a715944436745424d4155474179746c63414e42414f75510a71585a553532314c7a4454585878324559715675574379555a494a5a67526c2f4a477332526d5950594a435a756e304b6a3159497658356d425a3370433835770a30664a466d4d3142322b414373702b703651673d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a4343416979674177494241674955533356553735327371445566593630452f684571536e313432415577425159444b3256774d444d784d5441760a42674e56424155544b44417a4e5745784e6d45784e6a4e6d4d4455315a4442694f5445345a57526b596a51794f44426d4e32466c4f44426b5a5751314d6d45770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b4452690a4e7a55314e47566d4f575268593245344d7a55785a6a597a595751774e475a6c4d544579595452684e3251334f4751344d4455774b6a414642674d725a5841440a4951422f6f475854363775635978396c7078465a4652597674676d437942483232692f4c6e554e304b46364c73614f43415534776767464b4d423847413155640a497751594d42614146414e614671466a3846585175526a7432304b4139363641337455714d42304741315564446751574242524c645654766e61796f4e52396a0a7251542b4553704b66586a594254414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f77534231444342306142434245444a3657313561697079433255764d6971364943322f77466b7673466339504f72540a314e6e675a47666b65384a6c6e4f37385652555a6373463775687471797265796a697135695a485339684d304a3576494f78756a6f304945514a635a3738616c0a6e43744a69577143486a5467475a6a6f572b6c514a6a4a3955783530545478526545703365454f44394f3374347967645348347254467569754c3674646c5a380a72432f304b544334473576456f77476b51675241675150494251656d5a316973516f4635724b70506f747048584e38595978475935574651497a6b39647a37500a7a78496e5131716e4741736a51505353392b4a4d79774444416937584b754677526630576b32543954615944436745424d4155474179746c63414e424146556c0a5572545135717043634266504765546163584e776c35793357544667706a464b722b4d77367175736a2b62645a366c2b4e334378764f784a396d2b6939364d780a727054366b69536e417a6b2b327a67536941343d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d", + }, + { + Type: COSEKeyType, + In: TestCOSEKey, + Out: "d9022ea501020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c", + }, + { + Type: ThumbprintType, + In: TestThumbprint, + Out: "d9022d8201582068e656b251e67e8358bef8483ab0d51c6619f3e7a1a9f0e75838d41ff368f728", + }, + { + Type: CertThumbprintType, + In: TestThumbprint, + Out: "d9022f8201582068e656b251e67e8358bef8483ab0d51c6619f3e7a1a9f0e75838d41ff368f728", + }, + { + Type: CertPathThumbprintType, + In: TestThumbprint, + Out: "d902318201582068e656b251e67e8358bef8483ab0d51c6619f3e7a1a9f0e75838d41ff368f728", + }, + } { + key := MustNewCryptoKey(tv.In, tv.Type) + data, err := em.Marshal(key) + require.NoError(t, err) + + expected := MustHexDecode(t, tv.Out) + assert.Equal(t, expected, data) + + var key2 CryptoKey + err = dm.Unmarshal(data, &key2) + require.NoError(t, err) + assert.Equal(t, key.String(), key2.String()) + } +} + +func Test_NewCryptoKey_negative(t *testing.T) { + for _, tv := range []struct { + Type string + In any + ErrMsg string + }{ + { + Type: PKIXBase64KeyType, + In: 7, + ErrMsg: "value must be a string; found int", + }, + { + Type: PKIXBase64CertType, + In: 7, + ErrMsg: "value must be a string; found int", + }, + { + Type: PKIXBase64CertPathType, + In: 7, + ErrMsg: "value must be a string; found int", + }, + { + Type: COSEKeyType, + In: 7, + ErrMsg: "value must be a []byte; found int", + }, + { + Type: ThumbprintType, + In: 7, + ErrMsg: "value must be a swid.HashEntry; found int", + }, + { + Type: CertThumbprintType, + In: 7, + ErrMsg: "value must be a swid.HashEntry; found int", + }, + { + Type: CertPathThumbprintType, + In: 7, + ErrMsg: "value must be a swid.HashEntry; found int", + }, + { + Type: "random-key", + In: 7, + ErrMsg: "unexpected CryptoKey type: random-key", + }, + } { + + _, err := NewCryptoKey(tv.In, tv.Type) + assert.ErrorContains(t, err, tv.ErrMsg) + } +} diff --git a/comid/cryptokeys.go b/comid/cryptokeys.go new file mode 100644 index 00000000..98e9bd1e --- /dev/null +++ b/comid/cryptokeys.go @@ -0,0 +1,37 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import "fmt" + +// CryptoKeys is an array of *CryptoKey +type CryptoKeys []*CryptoKey + +// NewCryptoKeys instantiates an empty CryptoKeys +func NewCryptoKeys() *CryptoKeys { + return new(CryptoKeys) +} + +// Add the supplied *CryptoKey to the CryptoKeys +func (o *CryptoKeys) Add(v *CryptoKey) *CryptoKeys { + if o != nil && v != nil { + *o = append(*o, v) + } + return o +} + +// Valid returns an error if any of the contained keys fail to validate, or if +// CryptoKeys is empty +func (o CryptoKeys) Valid() error { + if len(o) == 0 { + return fmt.Errorf("no keys to validate") + } + + for i, vk := range o { + if err := vk.Valid(); err != nil { + return fmt.Errorf("invalid key at index %d: %w", i, err) + } + } + return nil +} diff --git a/comid/cryptokeys_test.go b/comid/cryptokeys_test.go new file mode 100644 index 00000000..2e8ba468 --- /dev/null +++ b/comid/cryptokeys_test.go @@ -0,0 +1,30 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_CryptoKeys(t *testing.T) { + keys := NewCryptoKeys() + err := keys.Valid() + + assert.EqualError(t, err, "no keys to validate") + + keys.Add(MustNewCOSEKey(TestCOSEKey)). + Add(MustNewPKIXBase64Key(TestECPubKey)). + Add(nil) + + err = keys.Valid() + assert.NoError(t, err) + + badKey := CryptoKey{TaggedPKIXBase64Cert("lol, nope!")} + keys.Add(&badKey) + + err = keys.Valid() + assert.ErrorContains(t, err, "invalid key at index 2") +} diff --git a/comid/devidentitykey.go b/comid/devidentitykey.go index 61a8317c..3d3bf7a4 100644 --- a/comid/devidentitykey.go +++ b/comid/devidentitykey.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2023 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -12,7 +12,7 @@ import "fmt" type DevIdentityKey struct { _ struct{} `cbor:",toarray"` Environment Environment `json:"environment"` - VerifKeys VerifKeys `json:"verification-keys"` + VerifKeys CryptoKeys `json:"verification-keys"` } func (o DevIdentityKey) Valid() error { diff --git a/comid/devidentitykey_test.go b/comid/devidentitykey_test.go index 94a8a2cc..14f2f6e1 100644 --- a/comid/devidentitykey_test.go +++ b/comid/devidentitykey_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2023 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -10,26 +10,27 @@ import ( ) func TestDevIdentityKey_Valid_empty(t *testing.T) { + invalidKey := CryptoKey{TaggedPKIXBase64Key("")} tvs := []struct { env Environment - verifkey VerifKeys + verifkey CryptoKeys testerr string }{ { env: Environment{}, - verifkey: VerifKeys{}, + verifkey: CryptoKeys{}, testerr: "environment validation failed: environment must not be empty", }, { env: Environment{Instance: NewInstanceUEID(TestUEID)}, - verifkey: VerifKeys{}, - testerr: "verification keys validation failed: no verification key to validate", + verifkey: CryptoKeys{}, + testerr: "verification keys validation failed: no keys to validate", }, { env: Environment{Instance: NewInstanceUEID(TestUEID)}, - verifkey: VerifKeys{{Key: ""}}, - testerr: "verification keys validation failed: invalid verification key at index 0: verification key not set", + verifkey: CryptoKeys{&invalidKey}, + testerr: "verification keys validation failed: invalid key at index 0: key value not set", }, } for _, tv := range tvs { diff --git a/comid/digests_test.go b/comid/digests_test.go index e81ce46d..141bb90e 100644 --- a/comid/digests_test.go +++ b/comid/digests_test.go @@ -110,7 +110,7 @@ func TestDigests_MarshalJSON(t *testing.T) { AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) require.NotNil(t, d) - expected := `[ "sha-256-32:5Ftyqw==", "sha-256-64:5Fty9cDAtXI=" ]` + expected := `[ "sha-256-32;5Ftyqw==", "sha-256-64;5Fty9cDAtXI=" ]` actual, err := json.Marshal(d) diff --git a/comid/example_psa_keys_test.go b/comid/example_psa_keys_test.go index 2e2909e4..edf95989 100644 --- a/comid/example_psa_keys_test.go +++ b/comid/example_psa_keys_test.go @@ -23,10 +23,10 @@ func Example_psa_keys() { // output: // ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 // InstanceID: 01ceebae7b8927a3227e5303cf5e0f1f7b34bb542ad7250ac03fbcde36ec2f1508 - // IAK public key: 4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145466e3074616f41775233506d724b6b594c74417344396f30354b534d366d6267664e436770754c306736567054486b5a6c3733776b354244786f56376e2b4f656565306949716b5733484d5a54334554696e694a64673d3d + // IAK public key: 2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d // ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 // InstanceID: 014ca3e4f50bf248c39787020d68ffd05c88767751bf2645ca923f57a98becd296 - // IAK public key: 4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741453656777165376879334f385970612b425545544c556a424e5533724558565579743958485237484a574c473758544b51643969316b565258654250444c466e66597275312f657578526e4a4d374839556f46444c64413d3d + // IAK public key: 2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d } func extractKeys(c *Comid) error { @@ -60,7 +60,7 @@ func extractPSAKey(k AttestVerifKey) error { return fmt.Errorf("more than one key") } - fmt.Printf("IAK public key: %x\n", k.VerifKeys[0].Key) + fmt.Printf("IAK public key: %x\n", k.VerifKeys[0]) return nil } diff --git a/comid/example_test.go b/comid/example_test.go index 50aa19b9..7f159d44 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -79,12 +79,9 @@ func Example_encode() { Environment: Environment{ Instance: NewInstanceUUID(uuid.UUID(TestUUID)), }, - VerifKeys: *NewVerifKeys(). - AddVerifKey( - NewVerifKey(). - SetKey("FGHIjkisldnASDxvWY..."). - AddCert("MENGsdhfugjQWEtyX..."). - AddCert("MIIEnjCCA4agAwIBA..."), + VerifKeys: *NewCryptoKeys(). + Add( + MustNewPKIXBase64Key(TestECPubKey), ), }, ).AddDevIdentityKey( @@ -92,12 +89,9 @@ func Example_encode() { Environment: Environment{ Instance: NewInstanceUEID(TestUEID), }, - VerifKeys: *NewVerifKeys(). - AddVerifKey( - NewVerifKey(). - SetKey("MIGkAgEBBDCk5QboB..."). - AddCert("MIIDkjCCAxigAwIBA..."). - AddCert("MIIEnjCCA4agAwIBA..."), + VerifKeys: *NewCryptoKeys(). + Add( + MustNewPKIXBase64Key(TestECPubKey), ), }, ) @@ -113,8 +107,8 @@ func Example_encode() { } // Output: - // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff030a04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff030b04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81a20075464748496a6b69736c646e415344787657592e2e2e0182744d454e477364686675676a5157457479582e2e2e744d4949456e6a43434134616741774942412e2e2e038182a101d902264702deadbeefdead81a200744d49476b416745424244436b3551626f422e2e2e0182744d4949446b6a43434178696741774942412e2e2e744d4949456e6a43434134616741774942412e2e2e - // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32:q83vAA==","sha-256-32://///w=="],"op-flags":["notSecure","debug"],"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32:q83vAA==","sha-256-32://///w=="],"op-flags":["notConfigured","notSecure","debug"],"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"key":"FGHIjkisldnASDxvWY...","chain":["MENGsdhfugjQWEtyX...","MIIEnjCCA4agAwIBA..."]}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"key":"MIGkAgEBBDCk5QboB...","chain":["MIIDkjCCAxigAwIBA...","MIIEnjCCA4agAwIBA..."]}]}]}} + //a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff030a04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff030b04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"op-flags":["notSecure","debug"],"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"op-flags":["notConfigured","notSecure","debug"],"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA() { @@ -150,10 +144,9 @@ func Example_encode_PSA() { Environment: Environment{ Instance: NewInstanceUEID(TestUEID), }, - VerifKeys: *NewVerifKeys(). - AddVerifKey( - NewVerifKey(). - SetKey("MIGkAgEBBDCk5QboB..."), + VerifKeys: *NewCryptoKeys(). + Add( + MustNewPKIXBase64Key(TestECPubKey), ), }, ) @@ -169,8 +162,8 @@ func Example_encode_PSA() { } // Output: - // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00028182a101d902264702deadbeefdead81a100744d49476b416745424244436b3551626f422e2e2e - // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32:q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32:q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"key":"MIGkAgEBBDCk5QboB..."}]}]}} + //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA_attestation_verification() { @@ -182,10 +175,9 @@ func Example_encode_PSA_attestation_verification() { Environment: Environment{ Instance: NewInstanceUEID(TestUEID), }, - VerifKeys: *NewVerifKeys(). - AddVerifKey( - NewVerifKey(). - SetKey("MFkwEwYHKoZI..."), + VerifKeys: *NewCryptoKeys(). + Add( + MustNewPKIXBase64Key(TestECPubKey), ), }, ) @@ -201,8 +193,8 @@ func Example_encode_PSA_attestation_verification() { } // Output: - // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a1028182a101d902264702deadbeefdead81a1006f4d466b77457759484b6f5a492e2e2e - // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"key":"MFkwEwYHKoZI..."}]}]}} + // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a1028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_decode_JSON() { @@ -324,8 +316,8 @@ func Example_decode_JSON() { "raw-value-mask": "qg==", "op-flags": [ "notSecure" ], "digests": [ - "sha-256:5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=", - "sha-384:S1bPoH+usqtX3pIeSpfWVRRLVGRw66qrb3HA21GN31tKX7KPsq0bSTQmRCTrHlqG" + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=", + "sha-384;S1bPoH+usqtX3pIeSpfWVRRLVGRw66qrb3HA21GN31tKX7KPsq0bSTQmRCTrHlqG" ], "version": { "scheme": "semaver", @@ -350,11 +342,8 @@ func Example_decode_JSON() { }, "verification-keys": [ { - "key": "MFkwEwYHKoZI...", - "chain": [ - "MIIDkjCCAxigAwIBA...", - "MIIEnjCCA4agAwIBA..." - ] + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" } ] } @@ -369,10 +358,12 @@ func Example_decode_JSON() { }, "verification-keys": [ { - "key": "MIIDUDCCAvWgAw..." + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" }, { - "key": "MIIEnjCCA4agAw..." + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" } ] } diff --git a/comid/test_vars.go b/comid/test_vars.go index 5d923cd6..5735189e 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2023 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/veraison/eat" + "github.com/veraison/swid" ) var ( @@ -33,6 +34,155 @@ var ( TestTagID = "urn:example:veraison" TestMKey uint64 = 700 TestCCALabel = "cca-platform-config" + + TestECPrivKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEICAm3+mCCDTMuzKqfZso9NT8ur9U9GjuUQ/lNEJvwRFMoAoGCCqGSM49 +AwEHoUQDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8BlLT4MFHOaO+ICTtIvrEeEpr/ +sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q== +-----END EC PRIVATE KEY-----` + + TestECPubKey = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B +lLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q== +-----END PUBLIC KEY-----` + + TestCert = `-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIUGhrA9M3yQIFqckA2v6nQewmF30IwCgYIKoZIzj0EAwIw +RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu +dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzA5MDQxMTAxNDhaGA8yMDUxMDEx +OTExMDE0OFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAf +BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABFtQb6hfv68vAVmu2RDFNcWGBxEPAZS0+DBRzmjviAk7SL6xHhKa +/7H0wD+uh9oQh3W+RxFyrUSqHekC3DizwNWjUzBRMB0GA1UdDgQWBBQWpNPb6eWD +SM/+jwpbzoO3iHg4LTAfBgNVHSMEGDAWgBQWpNPb6eWDSM/+jwpbzoO3iHg4LTAP +BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIAayNIF0eCJDZmcrqRjH +f9h8GxeIDUnLqldeIvNfa+9SAiEA9ULBTPjnTUhYle226OAjg2sdhkXtb3Mu0E0F +nuUmsIQ= +-----END CERTIFICATE-----` + + TestCertPath = `-----BEGIN CERTIFICATE----- +MIICejCCAiygAwIBAgIUIpeVwVhN/qYLgtNJlwZHJj+IT/wwBQYDK2VwMDMxMTAv +BgNVBAUTKDdhMDZlZWU0MWI3ODlmNDg2M2Q4NmI4Nzc4YjFhMjAxYTZmZWRkNTYw +IBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDIy +OTc5NWMxNTg0ZGZlYTYwYjgyZDM0OTk3MDY0NzI2M2Y4ODRmZmMwKjAFBgMrZXAD +IQAVUi7xVynM85UJ6lwVomvpSeOIB6XCbvkoFIfvSuZ7RqOCAU4wggFKMB8GA1Ud +IwQYMBaAFHoG7uQbeJ9IY9hrh3ixogGm/t1WMB0GA1UdDgQWBBQil5XBWE3+pguC +00mXBkcmP4hP/DAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK +KwYBBAHWeQIBGAEB/wSB1DCB0aBCBEAMtveoAJx4v3Qu9K8gI0J2kp7igBv4Vd9B +HEMtlfZOQdy6yDOlcPrQGYjSzwvAjFh8DA46bq+xGj31NFUy6pkho0IEQNCkl/Kf +bESLZ6OhEcdOnAziS5gx5TqJmF22yKCjIvLRIVxNIhZN2EnMAtm4dp1EGuPBLUrA +tzXhlzuZuK1xV6SkQgRASeSoHmnNgLhnnEKTWKzcL2jzPjOAFUQTNRy+iOghluI3 +ficG6NB7cMbLAZkfV12lihSV+/7iK3TJ0bUNjQgWpaYDCgEBMAUGAytlcANBAHu6 +DtuPNOurcAXc+41QY23hY8KRkBCKCE7phsiIwRfbxKMLldFGN5OytQfROQaWoAcv +IWTqV9JRzGQaGYnlLwE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICejCCAiygAwIBAgIUEbae7u1cP7s+G/CDb2Nu6nPRdYkwBQYDK2VwMDMxMTAv +BgNVBAUTKDIyOTc5NWMxNTg0ZGZlYTYwYjgyZDM0OTk3MDY0NzI2M2Y4ODRmZmMw +IBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDEx +YjY5ZWVlZWQ1YzNmYmIzZTFiZjA4MzZmNjM2ZWVhNzNkMTc1ODkwKjAFBgMrZXAD +IQDzgkTR7uvoP9NBzSEB9gu/lpd+NL38OVYQl0feiWKX+aOCAU4wggFKMB8GA1Ud +IwQYMBaAFCKXlcFYTf6mC4LTSZcGRyY/iE/8MB0GA1UdDgQWBBQRtp7u7Vw/uz4b +8INvY27qc9F1iTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK +KwYBBAHWeQIBGAEB/wSB1DCB0aBCBEBbsKgcavE+uy1AxkIdl7lN9ifHy3HE+Liu +8lME27CMY9kUtyw/les1H8vpmSyxhO4aTWgwuwQa7Yn9HoGweEHso0IEQHb870HN +1bUn9nFih11SBAj9lobpuJ5GrI/m+g6HwmoQz5Uly0oXMNnxEMA7fL2za01ynGpI +/uz82rUI2vLWSlGkQgRA3XFgIoVImosdAgvuPHVaobv3JGjGl3+ADOT1c6dT6dQE +dnObRNudY8qhzTvfEWR4eS6OJtfyrOeRyXek2OVJh6YDCgEBMAUGAytlcANBAJIj +yFqwdrZCSuYmC4+ZUUcANKQKA1KcRFiIlKcg/ppwKVykPXbAhsn6SCVqWGA7v7Ce +Li5hOrH/VljAQAcdYgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICejCCAiygAwIBAgIUYUXMcfV3Mucpunl193wuXxBBr/gwBQYDK2VwMDMxMTAv +BgNVBAUTKDExYjY5ZWVlZWQ1YzNmYmIzZTFiZjA4MzZmNjM2ZWVhNzNkMTc1ODkw +IBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDYx +NDVjYzcxZjU3NzMyZTcyOWJhNzk3NWY3N2MyZTVmMTA0MWFmZjgwKjAFBgMrZXAD +IQBJo9PgveHj0ahv8MkWHQUGSxZ/wSTdaNNZbdBZNa1L0aOCAU4wggFKMB8GA1Ud +IwQYMBaAFBG2nu7tXD+7Phvwg29jbupz0XWJMB0GA1UdDgQWBBRhRcxx9Xcy5ym6 +eXX3fC5fEEGv+DAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK +KwYBBAHWeQIBGAEB/wSB1DCB0aBCBEA2ansef0SbRN8j76w5hzW5/TCXFIsQcERs +bSKQYNnqug1rjECPnhe3A/8Z6WGxaDK1ehE+nrcvC9BRgrWpU67Jo0IEQIZyRCHK +9HUi/8y6V9P0ZuNEvmdpEdImQ09RU/lNPsXXxyv0VEmi6WDs4eFypmBR9LVXBXud +rCduuvyS6tBWsS6kQgRAbWRTCbXrd/qlLPII85IPB8pZ9uX+XgIHI4sSHf+3F6se +hA/80zUBzSi6Ozc0D+IbYYBYxdrXZEkn8iUWSdQokKYDCgEBMAUGAytlcANBAKlJ +/3VYalZm9XbEGTKrVRaoCVoUxQVH3udMrk9yoqjFowC4e3kdSBlGGf8mYEI7xvsA +ar1kf2bGXT/cEeFGIwM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICejCCAiygAwIBAgIUX+ivPHTOmvVktMnQGYQjuNlk/DUwBQYDK2VwMDMxMTAv +BgNVBAUTKDYxNDVjYzcxZjU3NzMyZTcyOWJhNzk3NWY3N2MyZTVmMTA0MWFmZjgw +IBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDVm +ZThhZjNjNzRjZTlhZjU2NGI0YzlkMDE5ODQyM2I4ZDk2NGZjMzUwKjAFBgMrZXAD +IQC6u3blwE4B1xdPMeUJP657P/m7iSt+HergvGbkkSxMrqOCAU4wggFKMB8GA1Ud +IwQYMBaAFGFFzHH1dzLnKbp5dfd8Ll8QQa/4MB0GA1UdDgQWBBRf6K88dM6a9WS0 +ydAZhCO42WT8NTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK +KwYBBAHWeQIBGAEB/wSB1DCB0aBCBEC4z2juJIx5jD7x6IuMNUi7TUomWxCQf9Qn +CJ91ozXk0vJ9nJO3RdveJvbvZhoPfDQIY8TiZp8UKDx4e+zW0cHko0IEQOhpMJ6G +EXLZgHtRAm81oXXACEF+nev2MCv6COhuRtFypG9B3foRm2rnFUbaVZs0pLfBMG8s +sSRJRcawXCimW4OkQgRA27Fgx7A4212qpqLaxaPd9tI+zpfKWrLYcLx20+DLfcqn +BIIpUCN30SuAu71se4x/ilcKuaWOO0qDg34SJEwFyqYDCgEBMAUGAytlcANBACtT +5Xrx659qGnywmlKHdlHO6Bd7fPboyzyIQhoEtFNuiD3WjDg/Vwz8cNCUkU+thG7f +C+WZhcpAckDldai+PAc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICejCCAiygAwIBAgIUXX0DVOylgEip7rzvyaNFegcDTZIwBQYDK2VwMDMxMTAv +BgNVBAUTKDVmZThhZjNjNzRjZTlhZjU2NGI0YzlkMDE5ODQyM2I4ZDk2NGZjMzUw +IBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDVk +N2QwMzU0ZWNhNTgwNDhhOWVlYmNlZmM5YTM0NTdhMDcwMzRkOTIwKjAFBgMrZXAD +IQCiaC2gHhMO1pbeQbUgLHhSgFBPD/zXNAGwAHsW272+c6OCAU4wggFKMB8GA1Ud +IwQYMBaAFF/orzx0zpr1ZLTJ0BmEI7jZZPw1MB0GA1UdDgQWBBRdfQNU7KWASKnu +vO/Jo0V6BwNNkjAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK +KwYBBAHWeQIBGAEB/wSB1DCB0aBCBEBYXwS/mrrX+D4MqzM8JTmIHC9XHqsJfOGc +b2fqBYPX0UQriLDRl1apHN22q1E+FeaLHWBE2uXda1Q6lYkQAaHio0IEQGKH8EAN +Mv1PMMbWsddZew2G/DR+A9tbSi7H680yBSe9Ce+gtabBarQDHpg9B8LebmoPpdXt +ATv+oSzzk+ZueVKkQgRAztbU2QzaJbcG5twEYjYAgFutCbngpg2t/2ez7QTNn4Nm +r94pOAx8LIpu6Cf/Wzcvd/4kLOvWxSb/buMqbGvrsqYDCgEBMAUGAytlcANBAMdx +fFnk52ru4fV5J1gsFBtlhy5mFbQnIRiGHLxBFaTJk9i+ixO5qFbRjqv7HQ/jGUsI +srUJQ4e2JEnSNakNcgE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICejCCAiygAwIBAgIUA1oWoWPwVdC5GO3bQoD3roDe1SowBQYDK2VwMDMxMTAv +BgNVBAUTKDVkN2QwMzU0ZWNhNTgwNDhhOWVlYmNlZmM5YTM0NTdhMDcwMzRkOTIw +IBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDAz +NWExNmExNjNmMDU1ZDBiOTE4ZWRkYjQyODBmN2FlODBkZWQ1MmEwKjAFBgMrZXAD +IQDSNEY1gbLMNAOC+3eok+RyQ6fhN8F23o2dx61QbsM0TqOCAU4wggFKMB8GA1Ud +IwQYMBaAFF19A1TspYBIqe6878mjRXoHA02SMB0GA1UdDgQWBBQDWhahY/BV0LkY +7dtCgPeugN7VKjAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK +KwYBBAHWeQIBGAEB/wSB1DCB0aBCBEA9mPpAmW+IEOXXOBgSy3ry53I562D9OZHZ ++DG1/M9mWxiUkRA1UciqMpGg6ngyqp38J5OpUIuFsoSVDqFVPyjxo0IEQIG/7h17 +Am77GLmQ1nSMBZjtrJ+FrmWTcZjxJ9cX0CPJqu7wugL5Tcj1I8c9nBNqsokFx8pE +tRoqiz7rt6Z52D2kQgRAZrvqFdyj4rVcjtVkJbMlp/8jmfGeaKh/RG64WrK2uNk9 +yhKOpkiQR0p5UsTam+XdEvqrxjLa43sr0di/pKEbZqYDCgEBMAUGAytlcANBAOuQ +qXZU521LzDTXXx2EYqVuWCyUZIJZgRl/JGs2RmYPYJCZun0Kj1YIvX5mBZ3pC85w +0fJFmM1B2+ACsp+p6Qg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICejCCAiygAwIBAgIUS3VU752sqDUfY60E/hEqSn142AUwBQYDK2VwMDMxMTAv +BgNVBAUTKDAzNWExNmExNjNmMDU1ZDBiOTE4ZWRkYjQyODBmN2FlODBkZWQ1MmEw +IBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDRi +NzU1NGVmOWRhY2E4MzUxZjYzYWQwNGZlMTEyYTRhN2Q3OGQ4MDUwKjAFBgMrZXAD +IQB/oGXT67ucYx9lpxFZFRYvtgmCyBH22i/LnUN0KF6LsaOCAU4wggFKMB8GA1Ud +IwQYMBaAFANaFqFj8FXQuRjt20KA966A3tUqMB0GA1UdDgQWBBRLdVTvnayoNR9j +rQT+ESpKfXjYBTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK +KwYBBAHWeQIBGAEB/wSB1DCB0aBCBEDJ6W15aipyC2UvMiq6IC2/wFkvsFc9POrT +1NngZGfke8JlnO78VRUZcsF7uhtqyreyjiq5iZHS9hM0J5vIOxujo0IEQJcZ78al +nCtJiWqCHjTgGZjoW+lQJjJ9Ux50TTxReEp3eEOD9O3t4ygdSH4rTFuiuL6tdlZ8 +rC/0KTC4G5vEowGkQgRAgQPIBQemZ1isQoF5rKpPotpHXN8YYxGY5WFQIzk9dz7P +zxInQ1qnGAsjQPSS9+JMywDDAi7XKuFwRf0Wk2T9TaYDCgEBMAUGAytlcANBAFUl +UrTQ5qpCcBfPGeTacXNwl5y3WTFgpjFKr+Mw6qusj+bdZ6l+N3CxvOxJ9m+i96Mx +rpT6kiSnAzk+2zgSiA4= +-----END CERTIFICATE-----` + + TestCOSEKey = MustHexDecode(nil, `a501020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c`) + + TestCOSEKeySetOne = MustHexDecode(nil, `81a501020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c`) + + TestCOSEKeySetMulti = MustHexDecode(nil, `82a501020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19ca601010327048202647369676e0543030201200621582015522ef15729ccf39509ea5c15a26be949e38807a5c26ef9281487ef4ae67b46`) + + TestThumbprint = swid.HashEntry{ + HashAlgID: 1, + HashValue: MustHexDecode(nil, `68e656b251e67e8358bef8483ab0d51c6619f3e7a1a9f0e75838d41ff368f728`), + } ) func MustHexDecode(t *testing.T, s string) []byte { @@ -167,7 +317,8 @@ var ( }, "verification-keys": [ { - "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==" + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" } ] }, @@ -188,7 +339,8 @@ var ( }, "verification-keys": [ { - "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Vwqe7hy3O8Ypa+BUETLUjBNU3rEXVUyt9XHR7HJWLG7XTKQd9i1kVRXeBPDLFnfYru1/euxRnJM7H9UoFDLdA==" + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" } ] } diff --git a/comid/verifkey.go b/comid/verifkey.go deleted file mode 100644 index e70c4429..00000000 --- a/comid/verifkey.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import "fmt" - -// VerifKey stores the verification key material associated to a signing key. -// Key is - typically, but not necessarily - a public key. Chain is an optional -// X.509 certificate chain corresponding to the public key in Key, encoded as an -// array of one or more base64-encoded DER PKIX certificates. The certificate -// containing the public key in Key MUST be the first certificate. This MAY be -// followed by additional certificates, with each subsequent certificate being -// the one used to certify the previous one. -type VerifKey struct { - Key string `cbor:"0,keyasint" json:"key"` - Chain *[]string `cbor:"1,keyasint,omitempty" json:"chain,omitempty"` -} - -// NewVerifKey instantiates an empty VerifKey -func NewVerifKey() *VerifKey { - return &VerifKey{} -} - -// SetKey sets the Key in the target object to the supplied value -func (o *VerifKey) SetKey(key string) *VerifKey { - if o != nil { - o.Key = key - } - return o -} - -// AddCert adds the supplied base64-encoded DER PKIX certificate in the target -// object -func (o *VerifKey) AddCert(cert string) *VerifKey { - if o != nil { - if o.Chain == nil { - o.Chain = new([]string) - } - *o.Chain = append(*o.Chain, cert) - } - return o -} - -func (o VerifKey) Valid() error { - if o.Key == "" { - return fmt.Errorf("verification key not set") - } - return nil -} diff --git a/comid/verifkeys.go b/comid/verifkeys.go deleted file mode 100644 index 37098a6c..00000000 --- a/comid/verifkeys.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import "fmt" - -// VerifKeys is an array of VerifKey -type VerifKeys []VerifKey - -// NewVerifKeys instantiates an empty VerifKeys array -func NewVerifKeys() *VerifKeys { - return new(VerifKeys) -} - -// AddVerifKey adds the supplied VerifKey to the target VerifKeys array -func (o *VerifKeys) AddVerifKey(v *VerifKey) *VerifKeys { - if o != nil && v != nil { - *o = append(*o, *v) - } - return o -} - -func (o VerifKeys) Valid() error { - if len(o) == 0 { - return fmt.Errorf("no verification key to validate") - } - - for i, vk := range o { - if err := vk.Valid(); err != nil { - return fmt.Errorf("invalid verification key at index %d: %w", i, err) - } - } - return nil -} diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 61a15646..147e89a7 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -184,10 +184,9 @@ func TestUnsignedCorim_Valid_ok(t *testing.T) { Environment: comid.Environment{ Instance: comid.NewInstanceUUID(uuid.UUID(comid.TestUUID)), }, - VerifKeys: *comid.NewVerifKeys(). - AddVerifKey( - comid.NewVerifKey(). - SetKey("FGHIjkisldnASDxvWY..."), + VerifKeys: *comid.NewCryptoKeys(). + Add( + comid.MustNewPKIXBase64Key(comid.TestECPubKey), ), }, ) diff --git a/cots/example_abbreviated_swid_tag_test.go b/cots/example_abbreviated_swid_tag_test.go index aba1c465..a4ca3cb4 100644 --- a/cots/example_abbreviated_swid_tag_test.go +++ b/cots/example_abbreviated_swid_tag_test.go @@ -117,5 +117,5 @@ func Example_completePrimaryTag() { fmt.Println(string(data)) // Output: - // + // } diff --git a/go.mod b/go.mod index b09d2f00..5560565b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/veraison/corim/v2 -go 1.16 +go 1.18 require ( github.com/fxamacker/cbor/v2 v2.4.0 @@ -13,6 +13,41 @@ require ( github.com/stretchr/testify v1.8.2 github.com/veraison/apiclient v0.2.0 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff - github.com/veraison/go-cose v1.0.0-rc.1 - github.com/veraison/swid v1.1.0 + github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7 + github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/goccy/go-json v0.9.11 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.1 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.4 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.0 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moogar0880/problems v0.1.1 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.63.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b78c681b..c6347523 100644 --- a/go.sum +++ b/go.sum @@ -25,607 +25,39 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= -cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= -cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= -cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= -cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= -cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= -cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= -cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= -cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= -cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= -cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= -cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= -cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= -cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= -cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= -cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= -cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= -cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= -cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= -cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= -cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= -cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= -cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= -cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= -cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= -cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= -cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= -cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= -cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= -cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= -cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= -cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= -cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= -cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= -cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= -cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= -cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= -cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= -cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= -cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= -cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= -cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= -cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= -cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= -cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= -cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= -cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= -cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= -cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= -cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= -cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= -cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= -cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= -cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= -cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= -cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= -cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= -cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= -cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= -cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= -cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= -cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= -cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= -cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= -cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= -cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= -cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= -cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= -cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= -cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= -cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= -cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= -cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= -cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= -cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= -cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= -cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= -cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= -cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= -cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= -cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= -cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= -cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= -cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= -cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= -cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= -cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= -cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= -cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= -cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= -cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= -cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= -cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= -cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= -cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= -cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= -cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= -cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= -cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= -cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= -cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= -cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= -cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= -cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= -cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= -cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= -cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= -cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= -cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= -cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= -cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= -cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= -cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= -cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= -cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= -cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= -cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= -cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= -cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= -cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= -cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= -cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= -cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= -cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= -cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= -cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= -cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= -cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= -cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= -cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= -cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= -cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= -cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= -cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= -cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= -cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= -cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= -cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= -cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= -cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= -cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= -cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= -cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= -cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= -cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= -cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= -cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= -cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= -cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= -cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= -cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= -cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= -cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= -cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= -cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= -cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= -cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= -cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= -cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= -cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= -cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= -cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= -cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= -cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= -cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= -cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= -cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= -cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= -cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= -cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= -cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= -cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= -cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= -cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= -cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= -cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= -cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= -cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= -cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= -cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= -cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= -cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= -cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= -cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= -cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= -cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= -cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= -cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= -cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= -cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= -cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= -cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= -cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= -cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= -cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= -cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= -cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= -cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= -cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= -cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= -cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= -cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= -cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= -cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= -cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= -cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= -cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= -cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= -cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= -cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= -cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= -cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= -cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= -cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= -cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= -cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= -cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= -cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= -cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= -cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= -cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= -cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= -cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= -cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= -cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= -cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= -cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= -cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= -cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= -cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= -cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= -cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= -cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= -cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= -cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= -cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= -cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= -cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= -cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= -cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= -cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= -cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= -cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= -cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= -cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= -cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= -cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= -cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= -cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= -cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= -cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= -cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= -cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= -cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= -cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= -cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= -cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= -cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= -cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= -cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= -cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= -cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= -cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= -cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= -cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= -cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= -cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= -cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= -cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= -cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= -cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= -cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= -cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= -cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= -cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= -cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= -cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= -github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= -github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= -github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -633,28 +65,16 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -662,18 +82,9 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -682,30 +93,17 @@ github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= -github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -736,10 +134,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -752,16 +148,12 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -778,37 +170,15 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= -github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -838,7 +208,6 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -847,24 +216,15 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -877,9 +237,6 @@ github.com/lestrrat-go/jwx/v2 v2.0.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9H github.com/lestrrat-go/jwx/v2 v2.0.8/go.mod h1:zLxnyv9rTlEvOUHbc48FAfIL8iYu2hHvIRaTFGc8mT0= github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -890,12 +247,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -918,11 +271,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= @@ -932,17 +280,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -950,7 +290,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= @@ -984,13 +323,12 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/veraison/apiclient v0.2.0 h1:QELvZ+eEfzh9v0ORe9B2UTMpiA7aONHpZIfwSfcRR6s= github.com/veraison/apiclient v0.2.0/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A= -github.com/veraison/cmw v0.1.0/go.mod h1:WoBrlgByc6C1FeHhdze1/bQx1kv5d1sWKO5ezEf4Hs4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff h1:r6I2eJL/z8dp5flsQIKHMeDjyV6UO8If3MaVBLvTjF4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= -github.com/veraison/go-cose v1.0.0-rc.1 h1:4qA7dbFJGvt7gcqv5MCIyCQvN+NpHFPkW7do3EeDLb8= -github.com/veraison/go-cose v1.0.0-rc.1/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= -github.com/veraison/swid v1.1.0 h1:jEf/jobG6j7r9W9HSj2jDi1IGGs7aMKyDgfGEMxQ6is= -github.com/veraison/swid v1.1.0/go.mod h1:d5jt76uMNbTfQ+f2qU4Lt8RvWOTsv6PFgstIM1QdMH0= +github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7 h1:KcKzBthSrSZIUEWBjVvkuk/DE3PyYFbXZxhx5byGFtc= +github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7/go.mod h1:t6V8WJzHm1PD5HNsuDjW3KLv577uWb6UTzbZGvdQHD8= +github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca h1:osmCKwWO/xM68Kz+rIXio1DNzEY2NdJOpGpoy5r8NlE= +github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca/go.mod h1:d5jt76uMNbTfQ+f2qU4Lt8RvWOTsv6PFgstIM1QdMH0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -998,10 +336,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -1012,10 +346,7 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= @@ -1029,43 +360,22 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1089,12 +399,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1134,30 +438,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1176,21 +457,6 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1204,12 +470,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1253,7 +513,6 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1268,50 +527,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1320,26 +539,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1354,7 +560,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1385,7 +590,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1397,27 +601,10 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1445,41 +632,7 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E= -google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= -google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1529,7 +682,6 @@ google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -1543,95 +695,6 @@ google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKr google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1657,22 +720,6 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1687,17 +734,12 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= @@ -1718,42 +760,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From eeb7bd486d3c3e2d115b264c657f82f3d20bfe4e Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Tue, 12 Sep 2023 15:47:10 +0100 Subject: [PATCH 010/110] Partial v2 revert Reverting module /v2 module name change. The reason is that go build system does not allow specifying a post-v1 module name unless there is a corresponding published release. Since we're not planning on publishing v2.0.0 until CoRIM spec is out of draft, we cannot rename the module until then. So for now, v2 development needs to happen on top of a v1 module name. Signed-off-by: Sergei Trofimov --- Makefile | 8 ++++---- README.md | 4 ++-- cocli/cmd/comidCreate.go | 2 +- cocli/cmd/comidCreate_test.go | 2 +- cocli/cmd/comidValidate.go | 2 +- cocli/cmd/common.go | 4 ++-- cocli/cmd/corimCreate.go | 6 +++--- cocli/cmd/corimDisplay.go | 4 ++-- cocli/cmd/corimExtract.go | 4 ++-- cocli/cmd/corimSign.go | 2 +- cocli/cmd/corimSubmit_test.go | 2 +- cocli/cmd/corimVerify.go | 2 +- cocli/cmd/cotsCreate.go | 2 +- cocli/cmd/test_vars.go | 2 +- cocli/main.go | 2 +- corim/cbor.go | 2 +- corim/entity.go | 2 +- corim/entity_test.go | 2 +- corim/meta.go | 2 +- corim/meta_test.go | 2 +- corim/signedcorim.go | 2 +- corim/unsignedcorim.go | 4 ++-- corim/unsignedcorim_test.go | 4 ++-- cots/cbor.go | 2 +- cots/cots.go | 2 +- cots/cots_test.go | 2 +- cots/env_group.go | 2 +- cots/env_group_test.go | 2 +- cots/example_test.go | 2 +- go.mod | 2 +- 30 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 1511e322..de492694 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,10 @@ SHELL := /bin/bash GO111MODULE := on -GOPKG := github.com/veraison/corim/v2/corim -GOPKG += github.com/veraison/corim/v2/comid -GOPKG += github.com/veraison/corim/v2/cots -GOPKG += github.com/veraison/corim/v2/cocli/cmd +GOPKG := github.com/veraison/corim/corim +GOPKG += github.com/veraison/corim/comid +GOPKG += github.com/veraison/corim/cots +GOPKG += github.com/veraison/corim/cocli/cmd MOCKGEN := $(shell go env GOPATH)/bin/mockgen INTERFACES := cocli/cmd/isubmitter.go diff --git a/README.md b/README.md index 6f9f5685..7dc87de8 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ [![linters](https://github.com/veraison/corim/actions/workflows/linters.yml/badge.svg)](https://github.com/veraison/corim/actions/workflows/linters.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/veraison/corim.svg)](https://pkg.go.dev/github.com/veraison/corim) -The [`corim/v2/corim`](corim) and [`corim/v2/comid`](comid) packages provide a golang API for low-level manipulation of [Concise Reference Integrity Manifest (CoRIM)](https://datatracker.ietf.org/doc/draft-ietf-rats-corim/) and Concise Module Identifier (CoMID) tags respectively. +The [`corim/corim`](corim) and [`corim/comid`](comid) packages provide a golang API for low-level manipulation of [Concise Reference Integrity Manifest (CoRIM)](https://datatracker.ietf.org/doc/draft-birkholz-rats-corim/) and Concise Module Identifier (CoMID) tags respectively. > [!NOTE] > These API are still in active development (as is the underlying CoRIM spec). > They are **subject to change** in the future. -The [`corim/v2/cocli`](cocli) package uses the API above (as well as the API from [`veraison/swid`](https://github.com/veraison/swid) package) to provide a user friendly command line interface for working with CoRIM, CoMID, CoSWID and CoTS. Specifically it allows creating, signing, verifying, displaying, uploading, and more. See [`cocli/README.md`](cocli/README.md) for further details. +The [`corim/cocli`](cocli) package uses the API above (as well as the API from [`veraison/swid`](https://github.com/veraison/swid) package) to provide a user friendly command line interface for working with CoRIM, CoMID, CoSWID and CoTS. Specifically it allows creating, signing, verifying, displaying, uploading, and more. See [`cocli/README.md`](cocli/README.md) for further details. ## Developer tips diff --git a/cocli/cmd/comidCreate.go b/cocli/cmd/comidCreate.go index b28a4847..bc70c0cc 100644 --- a/cocli/cmd/comidCreate.go +++ b/cocli/cmd/comidCreate.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) var ( diff --git a/cocli/cmd/comidCreate_test.go b/cocli/cmd/comidCreate_test.go index 63ee4ce3..b651ebd6 100644 --- a/cocli/cmd/comidCreate_test.go +++ b/cocli/cmd/comidCreate_test.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) func Test_ComidCreateCmd_unknown_argument(t *testing.T) { diff --git a/cocli/cmd/comidValidate.go b/cocli/cmd/comidValidate.go index f799796c..5fe7ff1f 100644 --- a/cocli/cmd/comidValidate.go +++ b/cocli/cmd/comidValidate.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) var ( diff --git a/cocli/cmd/common.go b/cocli/cmd/common.go index be8493f0..8ba508b0 100644 --- a/cocli/cmd/common.go +++ b/cocli/cmd/common.go @@ -10,8 +10,8 @@ import ( "strings" "github.com/spf13/afero" - "github.com/veraison/corim/v2/comid" - "github.com/veraison/corim/v2/cots" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/cots" "github.com/veraison/swid" ) diff --git a/cocli/cmd/corimCreate.go b/cocli/cmd/corimCreate.go index 34dbb92b..d4206464 100644 --- a/cocli/cmd/corimCreate.go +++ b/cocli/cmd/corimCreate.go @@ -9,9 +9,9 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/v2/comid" - "github.com/veraison/corim/v2/corim" - "github.com/veraison/corim/v2/cots" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" + "github.com/veraison/corim/cots" "github.com/veraison/swid" ) diff --git a/cocli/cmd/corimDisplay.go b/cocli/cmd/corimDisplay.go index 6650feb4..dfc9dcf2 100644 --- a/cocli/cmd/corimDisplay.go +++ b/cocli/cmd/corimDisplay.go @@ -11,8 +11,8 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/v2/corim" - "github.com/veraison/corim/v2/cots" + "github.com/veraison/corim/corim" + "github.com/veraison/corim/cots" ) var ( diff --git a/cocli/cmd/corimExtract.go b/cocli/cmd/corimExtract.go index f88dbd8b..80d39fd5 100644 --- a/cocli/cmd/corimExtract.go +++ b/cocli/cmd/corimExtract.go @@ -11,8 +11,8 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/v2/corim" - "github.com/veraison/corim/v2/cots" + "github.com/veraison/corim/corim" + "github.com/veraison/corim/cots" ) var ( diff --git a/cocli/cmd/corimSign.go b/cocli/cmd/corimSign.go index 67e52ae1..bf9bb8b2 100644 --- a/cocli/cmd/corimSign.go +++ b/cocli/cmd/corimSign.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/v2/corim" + "github.com/veraison/corim/corim" cose "github.com/veraison/go-cose" ) diff --git a/cocli/cmd/corimSubmit_test.go b/cocli/cmd/corimSubmit_test.go index 3d9ed4cd..5fa29d20 100644 --- a/cocli/cmd/corimSubmit_test.go +++ b/cocli/cmd/corimSubmit_test.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - mock_deps "github.com/veraison/corim/v2/cocli/cmd/mocks" + mock_deps "github.com/veraison/corim/cocli/cmd/mocks" ) func Test_CorimSubmitCmd_bad_server_url(t *testing.T) { diff --git a/cocli/cmd/corimVerify.go b/cocli/cmd/corimVerify.go index 24988890..6c5043d3 100644 --- a/cocli/cmd/corimVerify.go +++ b/cocli/cmd/corimVerify.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/v2/corim" + "github.com/veraison/corim/corim" ) var ( diff --git a/cocli/cmd/cotsCreate.go b/cocli/cmd/cotsCreate.go index 22686045..eb3c52dd 100644 --- a/cocli/cmd/cotsCreate.go +++ b/cocli/cmd/cotsCreate.go @@ -11,7 +11,7 @@ import ( "github.com/google/uuid" "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/veraison/corim/v2/cots" + "github.com/veraison/corim/cots" ) var ( diff --git a/cocli/cmd/test_vars.go b/cocli/cmd/test_vars.go index 7a452ba7..e4f05190 100644 --- a/cocli/cmd/test_vars.go +++ b/cocli/cmd/test_vars.go @@ -3,7 +3,7 @@ package cmd -import "github.com/veraison/corim/v2/comid" +import "github.com/veraison/corim/comid" var ( minimalCorimTemplate = []byte(`{ diff --git a/cocli/main.go b/cocli/main.go index 7af5ab14..c3fc2a3f 100644 --- a/cocli/main.go +++ b/cocli/main.go @@ -4,7 +4,7 @@ package main import ( - "github.com/veraison/corim/v2/cocli/cmd" + "github.com/veraison/corim/cocli/cmd" ) func main() { diff --git a/corim/cbor.go b/corim/cbor.go index 3b59c6bc..ec15e95f 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -7,7 +7,7 @@ import ( "reflect" cbor "github.com/fxamacker/cbor/v2" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) var ( diff --git a/corim/entity.go b/corim/entity.go index da8d56fa..49aa1468 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -6,7 +6,7 @@ package corim import ( "fmt" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) // Entity stores an entity-map capable of CBOR and JSON serializations. diff --git a/corim/entity_test.go b/corim/entity_test.go index 46c95bda..457b3770 100644 --- a/corim/entity_test.go +++ b/corim/entity_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) func TestEntity_Valid_uninitialized(t *testing.T) { diff --git a/corim/meta.go b/corim/meta.go index 25cc9d6f..45f1ea49 100644 --- a/corim/meta.go +++ b/corim/meta.go @@ -9,7 +9,7 @@ import ( "fmt" "time" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) type Signer struct { diff --git a/corim/meta_test.go b/corim/meta_test.go index ae37c16f..a4dd34cd 100644 --- a/corim/meta_test.go +++ b/corim/meta_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) var ( diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 7b5f27d8..755462e1 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -45,7 +45,7 @@ func (o *SignedCorim) processHdrs() error { // TODO(tho) key id is apparently mandatory, which doesn't look right. // TODO(tho) Check with the CoRIM design team. - // See https://github.com/veraison/corim/v2/issues/14 + // See https://github.com/veraison/corim/issues/14 v, ok = hdr.Protected[HeaderLabelCorimMeta] if !ok { diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 3aeb0856..68ca6786 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -9,9 +9,9 @@ import ( "fmt" "time" - "github.com/veraison/corim/v2/cots" + "github.com/veraison/corim/cots" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" "github.com/veraison/eat" "github.com/veraison/swid" ) diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 147e89a7..d9b4d105 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -11,8 +11,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/corim/v2/comid" - "github.com/veraison/corim/v2/cots" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/cots" "github.com/veraison/swid" ) diff --git a/cots/cbor.go b/cots/cbor.go index 1c4580a1..6d69ecd1 100644 --- a/cots/cbor.go +++ b/cots/cbor.go @@ -7,7 +7,7 @@ import ( "reflect" cbor "github.com/fxamacker/cbor/v2" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) var ( diff --git a/cots/cots.go b/cots/cots.go index 623557ba..477ac88a 100644 --- a/cots/cots.go +++ b/cots/cots.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" "github.com/veraison/swid" ) diff --git a/cots/cots_test.go b/cots/cots_test.go index cb0f2330..134f1d7d 100644 --- a/cots/cots_test.go +++ b/cots/cots_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) func TestConciseTaStore_Valid_no_environment_groups(t *testing.T) { diff --git a/cots/env_group.go b/cots/env_group.go index 7dc07433..01d98efb 100644 --- a/cots/env_group.go +++ b/cots/env_group.go @@ -7,7 +7,7 @@ import ( "encoding/json" "fmt" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" ) // EnvironmentGroup is the top-level representation of the unsigned-corim-map with diff --git a/cots/env_group_test.go b/cots/env_group_test.go index 3a6a1495..5cd41090 100644 --- a/cots/env_group_test.go +++ b/cots/env_group_test.go @@ -6,7 +6,7 @@ package cots import ( "testing" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" "github.com/veraison/swid" "github.com/stretchr/testify/assert" diff --git a/cots/example_test.go b/cots/example_test.go index 168c8962..f94ecbd7 100644 --- a/cots/example_test.go +++ b/cots/example_test.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/veraison/corim/v2/comid" + "github.com/veraison/corim/comid" "github.com/veraison/swid" ) diff --git a/go.mod b/go.mod index 5560565b..9d95f047 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/veraison/corim/v2 +module github.com/veraison/corim go 1.18 From 5a0612a36540ab6087fb8fdfb9a1af4bdba47007 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 14 Sep 2023 10:28:50 +0100 Subject: [PATCH 011/110] Update comid templates for Crypto Keys Update comid templates to use the correct format for keys, switching from the old representation to the new Crypto Key one. Signed-off-by: Sergei Trofimov --- cocli/data/templates/comid-psa-iakpub.json | 6 ++++-- cocli/data/templates/comid-psa-integ-iakpub.json | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cocli/data/templates/comid-psa-iakpub.json b/cocli/data/templates/comid-psa-iakpub.json index 03aaa6c7..47ef11d3 100644 --- a/cocli/data/templates/comid-psa-iakpub.json +++ b/cocli/data/templates/comid-psa-iakpub.json @@ -34,7 +34,8 @@ }, "verification-keys": [ { - "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==" + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" } ] }, @@ -55,7 +56,8 @@ }, "verification-keys": [ { - "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Vwqe7hy3O8Ypa+BUETLUjBNU3rEXVUyt9XHR7HJWLG7XTKQd9i1kVRXeBPDLFnfYru1/euxRnJM7H9UoFDLdA==" + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Vwqe7hy3O8Ypa+BUETLUjBNU3rEXVUyt9XHR7HJWLG7XTKQd9i1kVRXeBPDLFnfYru1/euxRnJM7H9UoFDLdA==\n-----END PUBLIC KEY-----" } ] } diff --git a/cocli/data/templates/comid-psa-integ-iakpub.json b/cocli/data/templates/comid-psa-integ-iakpub.json index 82e6826c..2f421794 100644 --- a/cocli/data/templates/comid-psa-integ-iakpub.json +++ b/cocli/data/templates/comid-psa-integ-iakpub.json @@ -34,7 +34,8 @@ }, "verification-keys": [ { - "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMKBCTNIcKUSDii11ySs3526iDZ8A\niTo7Tu6KPAqv7D7gS2XpJFbZiItSs3m9+9Ue6GnvHw/GW2ZZaVtszggXIw==\n-----END PUBLIC KEY-----" + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMKBCTNIcKUSDii11ySs3526iDZ8A\niTo7Tu6KPAqv7D7gS2XpJFbZiItSs3m9+9Ue6GnvHw/GW2ZZaVtszggXIw==\n-----END PUBLIC KEY-----" } ] }, @@ -55,7 +56,8 @@ }, "verification-keys": [ { - "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Vwqe7hy3O8Ypa+BUETLUjBNU3rEXVUyt9XHR7HJWLG7XTKQd9i1kVRXeBPDLFnfYru1/euxRnJM7H9UoFDLdA==" + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Vwqe7hy3O8Ypa+BUETLUjBNU3rEXVUyt9XHR7HJWLG7XTKQd9i1kVRXeBPDLFnfYru1/euxRnJM7H9UoFDLdA==\n-----END PUBLIC KEY-----" } ] } From 1bdfefa0e4b67d7f8e0f1d1eb3b336cde149854f Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 12 Oct 2023 16:11:31 +0100 Subject: [PATCH 012/110] .golangci.yml: remove deprecated linters Remove deprecated linters from .golangci.yml, ensuring that their replacements are present. Fix any new lint issues resulting from the same. Signed-off-by: Sergei Trofimov --- .golangci.yml | 12 ++++-------- comid/measurement_test.go | 4 ++-- comid/test_vars.go | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 44b32208..51cc9e70 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,9 +22,9 @@ linters-settings: - octalLiteral - paramTypeCombine - whyNoLint - - wrapperFunc + - wrapperFunc gofmt: - simplify: false + simplify: false goimports: golint: min-confidence: 0 @@ -40,24 +40,20 @@ linters-settings: linters: disable-all: true enable: - - deadcode - errcheck - goconst - gocyclo - gofmt - goimports - - golint - gosec - govet - ineffassign - - maligned - misspell + - revive - staticcheck - - structcheck - typecheck - unconvert - unused - - varcheck issues: @@ -72,7 +68,7 @@ issues: - goconst - dupl - gomnd - - lll + - lll - path: doc\.go linters: - goimports diff --git a/comid/measurement_test.go b/comid/measurement_test.go index e6ea5a2b..c9abed17 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -141,8 +141,8 @@ func TestMeasurement_NewUUIDMeasurement_bad_uuid(t *testing.T) { } var ( - testMKeyUintMin uint64 = 0 - testMKeyUintMax uint64 = ^uint64(0) + testMKeyUintMin uint64 + testMKeyUintMax = ^uint64(0) ) func TestMkey_Valid_no_value(t *testing.T) { diff --git a/comid/test_vars.go b/comid/test_vars.go index 5735189e..37b9be1a 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -201,7 +201,7 @@ func MustHexDecode(t *testing.T, s string) []byte { } func b64TestImplID() string { - var implID []byte = TestImplID[:] + var implID = TestImplID[:] return base64.StdEncoding.EncodeToString(implID) } From 0fe0afbaa4c15d159d84578db251c881c69fc658 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 6 Oct 2023 10:46:43 +0100 Subject: [PATCH 013/110] comid: replace opflag with FlagsMap Replace a single uint8 bitfield used to keep track of op flags with a flags map that allows tristate flags (true, false, and unset). This reflects flags representation in https://www.ietf.org/archive/id/draft-ietf-rats-corim-02.html Signed-off-by: Sergei Trofimov --- comid/flagsmap.go | 177 +++++++++++++++++++++++++++++++++++++++++ comid/flagsmap_test.go | 41 ++++++++++ comid/measurement.go | 84 ++++++++++++++++--- comid/opflag.go | 114 -------------------------- comid/opflag_test.go | 92 --------------------- 5 files changed, 290 insertions(+), 218 deletions(-) create mode 100644 comid/flagsmap.go create mode 100644 comid/flagsmap_test.go delete mode 100644 comid/opflag.go delete mode 100644 comid/opflag_test.go diff --git a/comid/flagsmap.go b/comid/flagsmap.go new file mode 100644 index 00000000..279cf1a4 --- /dev/null +++ b/comid/flagsmap.go @@ -0,0 +1,177 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +var True = true +var False = false + +// Flag indicates whether a particular operational mode is active within the +// measured environment. +type Flag int + +const ( + FlagIsConfigured Flag = iota + FlagIsSecure + FlagIsRecovery + FlagIsDebug + FlagIsReplayProtected + FlagIsIntegrityProtected + FlagIsRuntimeMeasured + FlagIsImmutable + FlagIsTcb +) + +// FlagsMap describes a number of boolean operational modes. If a value is nil, +// then the operational mode is unknown. +type FlagsMap struct { + // IsConfigured indicates whether the measured environment is fully + // configured for normal operation. + IsConfigured *bool `cbor:"0,keyasint,omitempty" json:"is-configured,omitempty"` + // IsSecure indicates whether the measured environment's configurable + // security settings are fully enabled. + IsSecure *bool `cbor:"1,keyasint,omitempty" json:"is-secure,omitempty"` + // IsRecovery indicates whether the measured environment is in recovery + // mode. + IsRecovery *bool `cbor:"2,keyasint,omitempty" json:"is-recovery,omitempty"` + // IsDebug indicates whether the measured environment is in a debug + // enabled mode. + IsDebug *bool `cbor:"3,keyasint,omitempty" json:"is-debug,omitempty"` + // IsReplayProtected indicates whether the measured environment is + // protected from replay by a previous image that differs from the + // current image. + IsReplayProtected *bool `cbor:"4,keyasint,omitempty" json:"is-replay-protected,omitempty"` + // IsIntegrityProtected indicates whether the measured environment is + // protected from unauthorized update. + IsIntegrityProtected *bool `cbor:"5,keyasint,omitempty" json:"is-integrity-protected,omitempty"` + // IsRuntimeMeasured indicates whether the measured environment is + // measured after being loaded into memory. + IsRuntimeMeasured *bool `cbor:"6,keyasint,omitempty" json:"is-runtime-meas,omitempty"` + // IsImmutable indicates whether the measured environment is immutable. + IsImmutable *bool `cbor:"7,keyasint,omitempty" json:"is-immutable,omitempty"` + // IsTcb indicates whether the measured environment is a trusted + // computing base. + IsTcb *bool `cbor:"8,keyasint,omitempty" json:"is-tcb,omitempty"` +} + +func NewFlagsMap() *FlagsMap { + return &FlagsMap{} +} + +func (o *FlagsMap) AnySet() bool { + if o.IsConfigured != nil || o.IsSecure != nil || o.IsRecovery != nil || o.IsDebug != nil || + o.IsReplayProtected != nil || o.IsIntegrityProtected != nil || + o.IsRuntimeMeasured != nil || o.IsImmutable != nil || o.IsTcb != nil { + return true + } + + return false +} + +func (o *FlagsMap) SetTrue(flags ...Flag) { + for _, flag := range flags { + switch flag { + case FlagIsConfigured: + o.IsConfigured = &True + case FlagIsSecure: + o.IsSecure = &True + case FlagIsRecovery: + o.IsRecovery = &True + case FlagIsDebug: + o.IsDebug = &True + case FlagIsReplayProtected: + o.IsReplayProtected = &True + case FlagIsIntegrityProtected: + o.IsIntegrityProtected = &True + case FlagIsRuntimeMeasured: + o.IsRuntimeMeasured = &True + case FlagIsImmutable: + o.IsImmutable = &True + case FlagIsTcb: + o.IsTcb = &True + default: + } + } +} + +func (o *FlagsMap) SetFalse(flags ...Flag) { + for _, flag := range flags { + switch flag { + case FlagIsConfigured: + o.IsConfigured = &False + case FlagIsSecure: + o.IsSecure = &False + case FlagIsRecovery: + o.IsRecovery = &False + case FlagIsDebug: + o.IsDebug = &False + case FlagIsReplayProtected: + o.IsReplayProtected = &False + case FlagIsIntegrityProtected: + o.IsIntegrityProtected = &False + case FlagIsRuntimeMeasured: + o.IsRuntimeMeasured = &False + case FlagIsImmutable: + o.IsImmutable = &False + case FlagIsTcb: + o.IsTcb = &False + default: + } + } +} + +func (o *FlagsMap) Clear(flags ...Flag) { + for _, flag := range flags { + switch flag { + case FlagIsConfigured: + o.IsConfigured = nil + case FlagIsSecure: + o.IsSecure = nil + case FlagIsRecovery: + o.IsRecovery = nil + case FlagIsDebug: + o.IsDebug = nil + case FlagIsReplayProtected: + o.IsReplayProtected = nil + case FlagIsIntegrityProtected: + o.IsIntegrityProtected = nil + case FlagIsRuntimeMeasured: + o.IsRuntimeMeasured = nil + case FlagIsImmutable: + o.IsImmutable = nil + case FlagIsTcb: + o.IsTcb = nil + default: + } + } +} + +func (o *FlagsMap) Get(flag Flag) *bool { + switch flag { + case FlagIsConfigured: + return o.IsConfigured + case FlagIsSecure: + return o.IsSecure + case FlagIsRecovery: + return o.IsRecovery + case FlagIsDebug: + return o.IsDebug + case FlagIsReplayProtected: + return o.IsReplayProtected + case FlagIsIntegrityProtected: + return o.IsIntegrityProtected + case FlagIsRuntimeMeasured: + return o.IsRuntimeMeasured + case FlagIsImmutable: + return o.IsImmutable + case FlagIsTcb: + return o.IsTcb + default: + return nil + } +} + +// Valid returns an error if the FlagsMap is invalid. +func (o FlagsMap) Valid() error { + return nil +} diff --git a/comid/flagsmap_test.go b/comid/flagsmap_test.go new file mode 100644 index 00000000..e9bc477c --- /dev/null +++ b/comid/flagsmap_test.go @@ -0,0 +1,41 @@ +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_FlagsMap(t *testing.T) { + fm := NewFlagsMap() + assert.False(t, fm.AnySet()) + + for _, flag := range []Flag{ + FlagIsConfigured, + FlagIsSecure, + FlagIsRecovery, + FlagIsDebug, + FlagIsReplayProtected, + FlagIsIntegrityProtected, + FlagIsRuntimeMeasured, + FlagIsImmutable, + FlagIsTcb, + } { + fm.SetTrue(flag) + assert.True(t, fm.AnySet()) + assert.Equal(t, true, *fm.Get(flag)) + + fm.SetFalse(flag) + assert.True(t, fm.AnySet()) + assert.Equal(t, false, *fm.Get(flag)) + + fm.Clear(flag) + assert.False(t, fm.AnySet()) + assert.Equal(t, (*bool)(nil), fm.Get(flag)) + } + + fm.SetTrue(Flag(-1)) + fm.SetFalse(Flag(-1)) + assert.False(t, fm.AnySet()) + assert.Equal(t, (*bool)(nil), fm.Get(Flag(-1))) +} diff --git a/comid/measurement.go b/comid/measurement.go index 4a560b67..b1909d87 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -8,6 +8,8 @@ import ( "fmt" "net" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" "github.com/veraison/eat" "github.com/veraison/swid" ) @@ -16,8 +18,9 @@ const MaxUint64 = ^uint64(0) // Measurement stores a measurement-map with CBOR and JSON serializations. type Measurement struct { - Key *Mkey `cbor:"0,keyasint,omitempty" json:"key,omitempty"` - Val Mval `cbor:"1,keyasint" json:"value"` + Key *Mkey `cbor:"0,keyasint,omitempty" json:"key,omitempty"` + Val Mval `cbor:"1,keyasint" json:"value"` + AuthorizedBy *CryptoKey `cbor:"2,keyasint,omitempty" json:"authorized-by,omitempty"` } // Mkey stores a $measured-element-type-choice. @@ -212,7 +215,7 @@ type Mval struct { Ver *Version `cbor:"0,keyasint,omitempty" json:"version,omitempty"` SVN *SVN `cbor:"1,keyasint,omitempty" json:"svn,omitempty"` Digests *Digests `cbor:"2,keyasint,omitempty" json:"digests,omitempty"` - OpFlags *OpFlags `cbor:"3,keyasint,omitempty" json:"op-flags,omitempty"` + Flags *FlagsMap `cbor:"3,keyasint,omitempty" json:"flags,omitempty"` RawValue *RawValue `cbor:"4,keyasint,omitempty" json:"raw-value,omitempty"` RawValueMask *[]byte `cbor:"5,keyasint,omitempty" json:"raw-value-mask,omitempty"` MACAddr *MACaddr `cbor:"6,keyasint,omitempty" json:"mac-addr,omitempty"` @@ -220,13 +223,35 @@ type Mval struct { SerialNumber *string `cbor:"8,keyasint,omitempty" json:"serial-number,omitempty"` UEID *eat.UEID `cbor:"9,keyasint,omitempty" json:"ueid,omitempty"` UUID *UUID `cbor:"10,keyasint,omitempty" json:"uuid,omitempty"` + + Extensions +} + +// RegisterExtensions registers a struct as a collections of extensions +func (o *Mval) RegisterExtensions(exts extensions.IExtensionsValue) { + o.Extensions.Register(exts) +} + +// GetExtensions returns pervisouosly registered extension +func (o *Mval) GetExtensions() extensions.IExtensionsValue { + return o.Extensions.IExtensionsValue +} + +// UnmarshalCBOR deserializes from CBOR +func (o *Mval) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +// MarshalCBOR serializes to CBOR +func (o *Mval) MarshalCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) } func (o Mval) Valid() error { if o.Ver == nil && o.SVN == nil && o.Digests == nil && - o.OpFlags == nil && + o.Flags == nil && o.RawValue == nil && o.RawValueMask == nil && o.MACAddr == nil && @@ -249,8 +274,8 @@ func (o Mval) Valid() error { } } - if o.OpFlags != nil { - if err := o.OpFlags.Valid(); err != nil { + if o.Flags != nil { + if err := o.Flags.Valid(); err != nil { return err } } @@ -259,7 +284,7 @@ func (o Mval) Valid() error { // TODO(tho) MAC addr & friends (see https://github.com/veraison/corim/issues/18) - return nil + return o.Extensions.ValidMval(&o) } // Version stores a version-map with JSON and CBOR serializations. @@ -450,16 +475,51 @@ func (o *Measurement) AddDigest(algID uint64, digest []byte) *Measurement { } o.Val.Digests = ds } + return o } -// SetOpFlags sets the supplied operational flags in the measurement-values-map -// of the target measurement -func (o *Measurement) SetOpFlags(flags ...OpFlags) *Measurement { +// SetFlagsTrue sets the supplied operational flags to true in the +// measurement-values-map of the target measurement +func (o *Measurement) SetFlagsTrue(flags ...Flag) *Measurement { + if o != nil { + if o.Val.Flags == nil { + o.Val.Flags = NewFlagsMap() + } + o.Val.Flags.SetTrue(flags...) + } + + return o +} + +// SetFlagsFalse sets the supplied operational flags to true in the +// measurement-values-map of the target measurement +func (o *Measurement) SetFlagsFalse(flags ...Flag) *Measurement { if o != nil { - o.Val.OpFlags = NewOpFlags() - o.Val.OpFlags.SetOpFlags(flags...) + if o.Val.Flags == nil { + o.Val.Flags = NewFlagsMap() + } + o.Val.Flags.SetFalse(flags...) } + + return o +} + +// ClearFlags clears the supplied operational flags in the +// measurement-values-map of the target measurement +func (o *Measurement) ClearFlags(flags ...Flag) *Measurement { + if o != nil { + if o.Val.Flags == nil { + return o + } + + o.Val.Flags.Clear(flags...) + + if !o.Val.Flags.AnySet() { + o.Val.Flags = nil + } + } + return o } diff --git a/comid/opflag.go b/comid/opflag.go deleted file mode 100644 index ce0c8d8b..00000000 --- a/comid/opflag.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import ( - "encoding/json" - "fmt" -) - -// OpFlags implements the flags-type, mapping to DiceTcbInfo.flags via the -// operational flags not-configured, not-secure, recovery and debug. -// If the flags field is omitted, all flags are assumed to be 0. -type OpFlags uint8 - -const ( - OpFlagNotConfigured OpFlags = 1 << iota - OpFlagNotSecure - OpFlagRecovery - OpFlagDebug -) - -func NewOpFlags() *OpFlags { - return new(OpFlags) -} - -func (o OpFlags) Strings() []string { - var a []string - - if o&OpFlagNotConfigured != 0 { - a = append(a, "notConfigured") - } - - if o&OpFlagNotSecure != 0 { - a = append(a, "notSecure") - } - - if o&OpFlagRecovery != 0 { - a = append(a, "recovery") - } - - if o&OpFlagDebug != 0 { - a = append(a, "debug") - } - - return a -} - -func (o OpFlags) Valid() error { - // While any combination in the lower half-byte is acceptable, the most - // significant nibble must be all zeroes. - if o&0xf0 != 0 { - return fmt.Errorf("op-flags has unknown bits asserted: %02x", o) - } - - return nil -} - -// SetFlags sets the target object as specified. As many flags as necessary can -// be specified in one call. -func (o *OpFlags) SetOpFlags(flags ...OpFlags) *OpFlags { - if o != nil { - for _, flag := range flags { - *o |= flag - } - } - return o -} - -func (o OpFlags) IsSet(flag OpFlags) bool { - return o&flag != 0 -} - -// UnmarshalJSON provides a custom deserializer for the OpFlags type that uses an -// array of identifiers rather than a bit set, e.g.: -// -// "op-flags": [ -// "notSecure", -// "debug" -// ] -func (o *OpFlags) UnmarshalJSON(data []byte) error { - var a []string - - if err := json.Unmarshal(data, &a); err != nil { - return err - } - - if len(a) == 0 { - *o = 0 - return nil - } - - for _, s := range a { - switch s { - case "notSecure": - *o |= OpFlagNotSecure - case "notConfigured": - *o |= OpFlagNotConfigured - case "recovery": - *o |= OpFlagRecovery - case "debug": - *o |= OpFlagDebug - default: - // ignore unknown opflags - continue - } - } - - return nil -} - -func (o OpFlags) MarshalJSON() ([]byte, error) { - return json.Marshal(o.Strings()) -} diff --git a/comid/opflag_test.go b/comid/opflag_test.go deleted file mode 100644 index 261dc963..00000000 --- a/comid/opflag_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFlags_UnmarshalJSON_skip_unknown(t *testing.T) { - tv := []byte(`[ "notSecure", "mysteriousFlagWhichWillBeIgnored" ]`) - - flags := NewOpFlags().SetOpFlags(OpFlagNotSecure) - require.NotNil(t, flags) - expected := *flags - - var actual OpFlags - err := actual.UnmarshalJSON(tv) - - assert.Nil(t, err) - assert.Equal(t, expected, actual) - assert.True(t, actual.IsSet(OpFlagNotSecure)) -} - -func TestFlags_UnmarshalJSON_all_known(t *testing.T) { - tv := []byte(`[ "notSecure", "notConfigured", "recovery", "debug" ]`) - - flags := NewOpFlags(). - SetOpFlags(OpFlagNotSecure). - SetOpFlags(OpFlagNotConfigured). - SetOpFlags(OpFlagRecovery). - SetOpFlags(OpFlagDebug) - require.NotNil(t, flags) - expected := *flags - - var actual OpFlags - err := actual.UnmarshalJSON(tv) - - fmt.Printf("CBOR: %02x\n", actual) - - assert.Nil(t, err) - assert.Equal(t, expected, actual) - assert.True(t, actual.IsSet(OpFlagNotSecure)) - assert.True(t, actual.IsSet(OpFlagRecovery)) - assert.True(t, actual.IsSet(OpFlagDebug)) - assert.True(t, actual.IsSet(OpFlagNotConfigured)) -} - -func TestFlags_UnmarshalJSON_empty(t *testing.T) { - tv := []byte(`[ ]`) - - flags := NewOpFlags() - require.NotNil(t, flags) - expected := *flags - - var actual OpFlags - err := actual.UnmarshalJSON(tv) - - fmt.Printf("%02x\n", actual) - - assert.Nil(t, err) - assert.Equal(t, expected, actual) - assert.False(t, actual.IsSet(OpFlagNotSecure)) - assert.False(t, actual.IsSet(OpFlagRecovery)) - assert.False(t, actual.IsSet(OpFlagDebug)) - assert.False(t, actual.IsSet(OpFlagNotConfigured)) -} - -func TestFlags_Valid_ok(t *testing.T) { - // all valid flags combinations - for i := 1; i <= 15; i++ { - tv := OpFlags(i) - - assert.Nil(t, tv.Valid()) - } -} - -func TestFlags_Valid_bad_combos(t *testing.T) { - for i := 1; i <= 15; i++ { - for j := 1; j <= 15; j++ { - tv := OpFlags(i<<4 | j) - - expectedErr := fmt.Sprintf("op-flags has unknown bits asserted: %02x", tv) - - assert.EqualError(t, tv.Valid(), expectedErr) - } - } -} From 596270d4871a2a4e629f72513250790ce8572454 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 6 Oct 2023 10:58:36 +0100 Subject: [PATCH 014/110] Implement struct extensions Implement struct extension mechanism and add extension points in places specified by https://www.ietf.org/archive/id/draft-ietf-rats-corim-02.html Extensions are implemented via Extensions object that allows registering user-defined structs are extensions. Fields from these user-defined structs are merged into the parent struct that embeds the Extensions object via some custom serialization code (this required updating the cbor dependency to v2.5.0, and go version to 1.19). Extensions also implements Viper*-like accessor methods for convenient access to the extension fields from the parent struct. (* see github.com/spf13/viper) Signed-off-by: Sergei Trofimov --- .github/workflows/ci-go-cover.yml | 2 +- .github/workflows/linters.yml | 2 +- Makefile | 2 + cocli/cmd/corimCreate_test.go | 4 +- cocli/cmd/corimDisplay_test.go | 2 +- cocli/cmd/corimExtract_test.go | 2 +- cocli/cmd/corimSign_test.go | 4 +- comid/comid.go | 24 +- comid/comid_test.go | 51 ++++ comid/cryptokey_test.go | 2 +- comid/entity.go | 46 +++- comid/example_test.go | 14 +- comid/extensions.go | 173 +++++++++++++ comid/extensions_test.go | 94 +++++++ comid/flagsmap.go | 46 +++- comid/measurement.go | 12 +- comid/measurement_test.go | 2 +- comid/referencevalue_test.go | 22 ++ comid/triples.go | 41 ++- corim/entity.go | 36 ++- corim/extensions.go | 68 +++++ corim/extensions_test.go | 86 +++++++ corim/meta.go | 36 ++- corim/meta_test.go | 12 + corim/signedcorim_test.go | 2 +- corim/unsignedcorim.go | 37 ++- corim/unsignedcorim_test.go | 8 + encoding/cbor.go | 409 ++++++++++++++++++++++++++++++ encoding/cbor_test.go | 279 ++++++++++++++++++++ encoding/embedded.go | 49 ++++ encoding/json.go | 319 +++++++++++++++++++++++ encoding/json_test.go | 121 +++++++++ extensions/extensions.go | 379 +++++++++++++++++++++++++++ extensions/extensions_test.go | 122 +++++++++ go.mod | 4 +- go.sum | 4 +- 36 files changed, 2472 insertions(+), 44 deletions(-) create mode 100644 comid/comid_test.go create mode 100644 comid/extensions.go create mode 100644 comid/extensions_test.go create mode 100644 comid/referencevalue_test.go create mode 100644 corim/extensions.go create mode 100644 corim/extensions_test.go create mode 100644 encoding/cbor.go create mode 100644 encoding/cbor_test.go create mode 100644 encoding/embedded.go create mode 100644 encoding/json.go create mode 100644 encoding/json_test.go create mode 100644 extensions/extensions.go create mode 100644 extensions/extensions_test.go diff --git a/.github/workflows/ci-go-cover.yml b/.github/workflows/ci-go-cover.yml index 94b48854..9dce92ca 100644 --- a/.github/workflows/ci-go-cover.yml +++ b/.github/workflows/ci-go-cover.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: "1.18" + go-version: "1.19" - name: Checkout code uses: actions/checkout@v2 - name: Install mockgen diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 665e7048..92368c61 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: "1.18" + go-version: "1.19" - name: Checkout code uses: actions/checkout@v2 - name: Install golangci-lint diff --git a/Makefile b/Makefile index de492694..f5e8bbdb 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ GOPKG := github.com/veraison/corim/corim GOPKG += github.com/veraison/corim/comid GOPKG += github.com/veraison/corim/cots GOPKG += github.com/veraison/corim/cocli/cmd +GOPKG += github.com/veraison/corim/encoding +GOPKG += github.com/veraison/corim/extensions MOCKGEN := $(shell go env GOPATH)/bin/mockgen INTERFACES := cocli/cmd/isubmitter.go diff --git a/cocli/cmd/corimCreate_test.go b/cocli/cmd/corimCreate_test.go index 33f112b8..b2dfb770 100644 --- a/cocli/cmd/corimCreate_test.go +++ b/cocli/cmd/corimCreate_test.go @@ -117,7 +117,7 @@ func Test_CorimCreateCmd_with_a_bad_comid(t *testing.T) { cmd.SetArgs(args) err = cmd.Execute() - assert.EqualError(t, err, `error loading CoMID from bad-comid.cbor: cbor: unexpected "break" code`) + assert.EqualError(t, err, `error loading CoMID from bad-comid.cbor: expected map (CBOR Major Type 5), found Major Type 7`) } func Test_CorimCreateCmd_with_an_invalid_comid(t *testing.T) { @@ -138,7 +138,7 @@ func Test_CorimCreateCmd_with_an_invalid_comid(t *testing.T) { cmd.SetArgs(args) err = cmd.Execute() - assert.EqualError(t, err, `error adding CoMID from invalid-comid.cbor (check its validity using the "comid validate" sub-command)`) + assert.EqualError(t, err, `error loading CoMID from invalid-comid.cbor: missing mandatory field "Triples" (4)`) } func Test_CorimCreateCmd_with_a_bad_coswid(t *testing.T) { diff --git a/cocli/cmd/corimDisplay_test.go b/cocli/cmd/corimDisplay_test.go index 7f87cf25..5e1ccb47 100644 --- a/cocli/cmd/corimDisplay_test.go +++ b/cocli/cmd/corimDisplay_test.go @@ -76,7 +76,7 @@ func Test_CorimDisplayCmd_invalid_signed_corim(t *testing.T) { require.NoError(t, err) err = cmd.Execute() - assert.EqualError(t, err, "error decoding signed CoRIM from invalid.cbor: failed validation of unsigned CoRIM: empty id") + assert.EqualError(t, err, `error decoding signed CoRIM from invalid.cbor: failed CBOR decoding of unsigned CoRIM: unexpected EOF`) } func Test_CorimDisplayCmd_ok_top_level_view(t *testing.T) { diff --git a/cocli/cmd/corimExtract_test.go b/cocli/cmd/corimExtract_test.go index 2ec748d1..8b476c98 100644 --- a/cocli/cmd/corimExtract_test.go +++ b/cocli/cmd/corimExtract_test.go @@ -76,7 +76,7 @@ func Test_CorimExtractCmd_invalid_signed_corim(t *testing.T) { require.NoError(t, err) err = cmd.Execute() - assert.EqualError(t, err, "error decoding signed CoRIM from invalid.cbor: failed validation of unsigned CoRIM: empty id") + assert.EqualError(t, err, `error decoding signed CoRIM from invalid.cbor: failed CBOR decoding of unsigned CoRIM: unexpected EOF`) } func Test_CorimExtractCmd_ok_save_to_default_dir(t *testing.T) { diff --git a/cocli/cmd/corimSign_test.go b/cocli/cmd/corimSign_test.go index 27956a4f..bc460376 100644 --- a/cocli/cmd/corimSign_test.go +++ b/cocli/cmd/corimSign_test.go @@ -91,7 +91,7 @@ func Test_CorimSignCmd_bad_unsigned_corim(t *testing.T) { require.NoError(t, err) err = cmd.Execute() - assert.EqualError(t, err, "error decoding unsigned CoRIM from bad.txt: unexpected EOF") + assert.EqualError(t, err, "error decoding unsigned CoRIM from bad.txt: expected map (CBOR Major Type 5), found Major Type 3") } func Test_CorimSignCmd_invalid_unsigned_corim(t *testing.T) { @@ -109,7 +109,7 @@ func Test_CorimSignCmd_invalid_unsigned_corim(t *testing.T) { require.NoError(t, err) err = cmd.Execute() - assert.EqualError(t, err, "error validating CoRIM: tags validation failed: no tags") + assert.EqualError(t, err, `error decoding unsigned CoRIM from invalid.cbor: missing mandatory field "Tags" (1)`) } func Test_CorimSignCmd_non_existent_meta_file(t *testing.T) { diff --git a/comid/comid.go b/comid/comid.go index ea9779c7..48eae969 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -8,6 +8,8 @@ import ( "fmt" "net/url" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" "github.com/veraison/swid" ) @@ -19,6 +21,8 @@ type Comid struct { Entities *Entities `cbor:"2,keyasint,omitempty" json:"entities,omitempty"` LinkedTags *LinkedTags `cbor:"3,keyasint,omitempty" json:"linked-tags,omitempty"` Triples Triples `cbor:"4,keyasint" json:"triples"` + + Extensions } // NewComid instantiates an empty Comid @@ -26,6 +30,16 @@ func NewComid() *Comid { return &Comid{} } +// RegisterExtensions registers a struct as a collections of extensions +func (o *Comid) RegisterExtensions(exts extensions.IExtensionsValue) { + o.Extensions.Register(exts) +} + +// GetExtensions returns pervisouosly registered extension +func (o *Comid) GetExtensions() extensions.IExtensionsValue { + return o.Extensions.IExtensionsValue +} + // SetLanguage sets the language used in the target Comid to the supplied // language tag. See also: BCP 47 and the IANA Language subtag registry. func (o *Comid) SetLanguage(language string) *Comid { @@ -229,7 +243,7 @@ func (o Comid) Valid() error { return fmt.Errorf("triples validation failed: %w", err) } - return nil + return o.Extensions.validComid(&o) } // ToCBOR serializes the target Comid to CBOR @@ -238,17 +252,17 @@ func (o Comid) ToCBOR() ([]byte, error) { return nil, err } - return em.Marshal(&o) + return encoding.SerializeStructToCBOR(em, &o) } // FromCBOR deserializes a CBOR-encoded CoMID into the target Comid func (o *Comid) FromCBOR(data []byte) error { - return dm.Unmarshal(data, o) + return encoding.PopulateStructFromCBOR(dm, data, o) } // FromJSON deserializes a JSON-encoded CoMID into the target Comid func (o *Comid) FromJSON(data []byte) error { - return json.Unmarshal(data, o) + return encoding.PopulateStructFromJSON(data, o) } // ToJSON serializes the target Comid to JSON @@ -257,7 +271,7 @@ func (o Comid) ToJSON() ([]byte, error) { return nil, err } - return json.Marshal(&o) + return encoding.SerializeStructToJSON(&o) } func (o Comid) ToJSONPretty(indent string) ([]byte, error) { diff --git a/comid/comid_test.go b/comid/comid_test.go new file mode 100644 index 00000000..a8cdf198 --- /dev/null +++ b/comid/comid_test.go @@ -0,0 +1,51 @@ +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/swid" +) + +func Test_Comid_Extensions(t *testing.T) { + c := NewComid() + assert.Nil(t, c.GetExtensions()) + assert.Equal(t, "", c.MustGetString("field-one")) + + err := c.Set("field-one", "foo") + assert.EqualError(t, err, "extension not found: field-one") + + type ComidExt struct { + FieldOne string `cbor:"-1,keyasint" json:"field-one"` + } + + c.RegisterExtensions(&ComidExt{}) + + err = c.Set("field-one", "foo") + assert.NoError(t, err) + assert.Equal(t, "foo", c.MustGetString("-1")) +} + +func Test_Comid_ToJSONPretty(t *testing.T) { + c := NewComid() + + _, err := c.ToJSONPretty(" ") + assert.EqualError(t, err, "tag-identity validation failed: empty tag-id") + + c.TagIdentity = TagIdentity{TagID: *swid.NewTagID("test"), TagVersion: 1} + c.Triples = Triples{ReferenceValues: &[]ReferenceValue{}} + + expected := `{ + "tag-identity": { + "id": "test", + "version": 1 + }, + "triples": { + "reference-values": [] + } +}` + v, err := c.ToJSONPretty(" ") + require.NoError(t, err) + assert.Equal(t, expected, string(v)) +} diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index e1a3b4f8..1c0812fc 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -119,7 +119,7 @@ func Test_CryptoKey_NewCOSEKey(t *testing.T) { assert.EqualError(t, err, "empty COSE_Key bytes") _, err = NewCOSEKey([]byte("DEADBEEF")) - assert.Contains(t, err.Error(), "cbor: cannot unmarshal") + assert.Contains(t, err.Error(), "cbor: 3 bytes of extraneous data starting at index 5") badKey := []byte{ // taken from go-cose unit tests 0xa2, // map(2) diff --git a/comid/entity.go b/comid/entity.go index 694fef6c..70399a7e 100644 --- a/comid/entity.go +++ b/comid/entity.go @@ -3,7 +3,12 @@ package comid -import "fmt" +import ( + "fmt" + + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" +) type TaggedURI string @@ -16,8 +21,21 @@ type Entity struct { EntityName string `cbor:"0,keyasint" json:"name"` RegID *TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` Roles Roles `cbor:"2,keyasint" json:"roles"` + + Extensions +} + +// RegisterExtensions registers a struct as a collections of extensions +func (o *Entity) RegisterExtensions(exts extensions.IExtensionsValue) { + o.Extensions.Register(exts) } +// GetExtensions returns pervisouosly registered extension +func (o *Entity) GetExtensions() extensions.IExtensionsValue { + return o.Extensions.IExtensionsValue +} + +// SetEntityName is used to set the EntityName field of Entity using supplied name func (o *Entity) SetEntityName(name string) *Entity { if o != nil { if name == "" { @@ -28,6 +46,7 @@ func (o *Entity) SetEntityName(name string) *Entity { return o } +// SetRegID is used to set the RegID field of Entity using supplied uri func (o *Entity) SetRegID(uri string) *Entity { if o != nil { if uri == "" { @@ -39,6 +58,7 @@ func (o *Entity) SetRegID(uri string) *Entity { return o } +// SetRoles appends the supplied roles to the target entity. func (o *Entity) SetRoles(roles ...Role) *Entity { if o != nil { o.Roles.Add(roles...) @@ -46,6 +66,7 @@ func (o *Entity) SetRoles(roles ...Role) *Entity { return o } +// Valid checks for validity of the fields within each Entity func (o Entity) Valid() error { if o.EntityName == "" { return fmt.Errorf("invalid entity: empty entity-name") @@ -59,7 +80,27 @@ func (o Entity) Valid() error { return fmt.Errorf("invalid entity: %w", err) } - return nil + return o.Extensions.validEntity(&o) +} + +// UnmarshalCBOR deserializes from CBOR +func (o *Entity) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +// MarshalCBOR serializes to CBOR +func (o *Entity) MarshalCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) +} + +// UnmarshalJSON deserializes from JSON +func (o *Entity) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// MarshalJSON serializes to JSON +func (o *Entity) MarshalJSON() ([]byte, error) { + return encoding.SerializeStructToJSON(o) } // Entities is an array of entity-map's @@ -78,6 +119,7 @@ func (o *Entities) AddEntity(e Entity) *Entities { return o } +// Valid iterates over the range of individual entities to check for validity func (o Entities) Valid() error { for i, m := range o { if err := m.Valid(); err != nil { diff --git a/comid/example_test.go b/comid/example_test.go index 7f159d44..756829d3 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -37,7 +37,8 @@ func Example_encode() { SetSVN(2). AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). - SetOpFlags(OpFlagNotSecure, OpFlagDebug). + SetFlagsTrue(FlagIsDebug). + SetFlagsFalse(FlagIsSecure). SetSerialNumber("C02X70VHJHD5"). SetUEID(TestUEID). SetUUID(TestUUID). @@ -65,7 +66,8 @@ func Example_encode() { SetMinSVN(2). AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). - SetOpFlags(OpFlagNotSecure, OpFlagDebug, OpFlagNotConfigured). + SetFlagsTrue(FlagIsDebug). + SetFlagsFalse(FlagIsSecure, FlagIsConfigured). SetSerialNumber("C02X70VHJHD5"). SetUEID(TestUEID). SetUUID(TestUUID). @@ -107,8 +109,8 @@ func Example_encode() { } // Output: - //a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff030a04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff030b04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"op-flags":["notSecure","debug"],"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"op-flags":["notConfigured","notSecure","debug"],"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA() { @@ -162,8 +164,8 @@ func Example_encode_PSA() { } // Output: - //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA_attestation_verification() { diff --git a/comid/extensions.go b/comid/extensions.go new file mode 100644 index 00000000..df9d0d2f --- /dev/null +++ b/comid/extensions.go @@ -0,0 +1,173 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package comid + +import ( + "github.com/veraison/corim/extensions" +) + +type IComidConstrainer interface { + ConstrainComid(*Comid) error +} + +type ITriplesConstrainer interface { + ValidTriples(*Triples) error +} + +type IMvalConstrainer interface { + ConstrainMval(*Mval) error +} + +type IEntityConstrainer interface { + ConstrainEntity(*Entity) error +} + +type IFlagsMapConstrainer interface { + ConstrainFlagsMap(*FlagsMap) error +} + +type IFlagSetter interface { + AnySet() bool + SetTrue(Flag) + SetFalse(Flag) + Clear(Flag) + Get(Flag) *bool +} + +type Extensions struct { + extensions.Extensions +} + +func (o *Extensions) validComid(comid *Comid) error { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(IComidConstrainer) + if ok { + if err := ev.ConstrainComid(comid); err != nil { + return err + } + } + + return nil +} + +func (o *Extensions) validTriples(triples *Triples) error { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(ITriplesConstrainer) + if ok { + if err := ev.ValidTriples(triples); err != nil { + return err + } + } + + return nil +} + +func (o *Extensions) validMval(triples *Mval) error { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(IMvalConstrainer) + if ok { + if err := ev.ConstrainMval(triples); err != nil { + return err + } + } + + return nil +} + +func (o *Extensions) validEntity(triples *Entity) error { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(IEntityConstrainer) + if ok { + if err := ev.ConstrainEntity(triples); err != nil { + return err + } + } + + return nil +} + +func (o *Extensions) validFlagsMap(triples *FlagsMap) error { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(IFlagsMapConstrainer) + if ok { + if err := ev.ConstrainFlagsMap(triples); err != nil { + return err + } + } + + return nil +} + +func (o *Extensions) setTrue(flag Flag) { + if !o.HaveExtensions() { + return + } + + ev, ok := o.IExtensionsValue.(IFlagSetter) + if ok { + ev.SetTrue(flag) + } +} + +func (o *Extensions) setFalse(flag Flag) { + if !o.HaveExtensions() { + return + } + + ev, ok := o.IExtensionsValue.(IFlagSetter) + if ok { + ev.SetFalse(flag) + } +} + +func (o *Extensions) clear(flag Flag) { + if !o.HaveExtensions() { + return + } + + ev, ok := o.IExtensionsValue.(IFlagSetter) + if ok { + ev.Clear(flag) + } +} + +func (o *Extensions) get(flag Flag) *bool { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(IFlagSetter) + if ok { + return ev.Get(flag) + } + + return nil +} + +func (o *Extensions) anySet() bool { + if !o.HaveExtensions() { + return false + } + + ev, ok := o.IExtensionsValue.(IFlagSetter) + if ok { + return ev.AnySet() + } + + return false +} diff --git a/comid/extensions_test.go b/comid/extensions_test.go new file mode 100644 index 00000000..2d96f709 --- /dev/null +++ b/comid/extensions_test.go @@ -0,0 +1,94 @@ +package comid + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +var FlagTestFlag = Flag(-1) + +type TestExtension struct { + TestFlag *bool +} + +func (o *TestExtension) ConstrainComid(v *Comid) error { + return errors.New("invalid") +} + +func (o *TestExtension) ValidTriples(v *Triples) error { + return errors.New("invalid") +} + +func (o *TestExtension) ConstrainMval(v *Mval) error { + return errors.New("invalid") +} + +func (o *TestExtension) ConstrainFlagsMap(v *FlagsMap) error { + return errors.New("invalid") +} + +func (o *TestExtension) ConstrainEntity(v *Entity) error { + return errors.New("invalid") +} + +func (o *TestExtension) SetTrue(flag Flag) { + if flag == FlagTestFlag { + o.TestFlag = &True + } +} +func (o *TestExtension) SetFalse(flag Flag) { + if flag == FlagTestFlag { + o.TestFlag = &False + } +} + +func (o *TestExtension) Clear(flag Flag) { + if flag == FlagTestFlag { + o.TestFlag = nil + } +} + +func (o *TestExtension) Get(flag Flag) *bool { + if flag == FlagTestFlag { + return o.TestFlag + } + + return nil +} + +func (o *TestExtension) AnySet() bool { + return o.TestFlag != nil +} + +func Test_Extensions(t *testing.T) { + exts := Extensions{} + exts.Register(&TestExtension{}) + + err := exts.validComid(nil) + assert.EqualError(t, err, "invalid") + + err = exts.validTriples(nil) + assert.EqualError(t, err, "invalid") + + err = exts.validMval(nil) + assert.EqualError(t, err, "invalid") + + err = exts.validEntity(nil) + assert.EqualError(t, err, "invalid") + + err = exts.validFlagsMap(nil) + assert.EqualError(t, err, "invalid") + + assert.False(t, exts.anySet()) + + exts.setTrue(FlagTestFlag) + + exts.setFalse(FlagTestFlag) + assert.False(t, *exts.get(FlagTestFlag)) + + exts.clear(FlagTestFlag) + assert.Nil(t, exts.get(FlagTestFlag)) + assert.False(t, exts.anySet()) +} diff --git a/comid/flagsmap.go b/comid/flagsmap.go index 279cf1a4..94ed3e35 100644 --- a/comid/flagsmap.go +++ b/comid/flagsmap.go @@ -3,6 +3,11 @@ package comid +import ( + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" +) + var True = true var False = false @@ -52,6 +57,8 @@ type FlagsMap struct { // IsTcb indicates whether the measured environment is a trusted // computing base. IsTcb *bool `cbor:"8,keyasint,omitempty" json:"is-tcb,omitempty"` + + Extensions } func NewFlagsMap() *FlagsMap { @@ -65,7 +72,7 @@ func (o *FlagsMap) AnySet() bool { return true } - return false + return o.Extensions.anySet() } func (o *FlagsMap) SetTrue(flags ...Flag) { @@ -90,6 +97,7 @@ func (o *FlagsMap) SetTrue(flags ...Flag) { case FlagIsTcb: o.IsTcb = &True default: + o.Extensions.setTrue(flag) } } } @@ -116,6 +124,7 @@ func (o *FlagsMap) SetFalse(flags ...Flag) { case FlagIsTcb: o.IsTcb = &False default: + o.Extensions.setFalse(flag) } } } @@ -142,6 +151,7 @@ func (o *FlagsMap) Clear(flags ...Flag) { case FlagIsTcb: o.IsTcb = nil default: + o.Extensions.clear(flag) } } } @@ -167,11 +177,41 @@ func (o *FlagsMap) Get(flag Flag) *bool { case FlagIsTcb: return o.IsTcb default: - return nil + return o.Extensions.get(flag) } } +// RegisterExtensions registers a struct as a collections of extensions +func (o *FlagsMap) RegisterExtensions(exts extensions.IExtensionsValue) { + o.Extensions.Register(exts) +} + +// GetExtensions returns pervisouosly registered extension +func (o *FlagsMap) GetExtensions() extensions.IExtensionsValue { + return o.Extensions.IExtensionsValue +} + +// UnmarshalCBOR deserializes from CBOR +func (o *FlagsMap) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +// MarshalCBOR serializes to CBOR +func (o *FlagsMap) MarshalCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) +} + +// UnmarshalJSON deserializes from JSON +func (o *FlagsMap) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// MarshalJSON serializes to JSON +func (o *FlagsMap) MarshalJSON() ([]byte, error) { + return encoding.SerializeStructToJSON(o) +} + // Valid returns an error if the FlagsMap is invalid. func (o FlagsMap) Valid() error { - return nil + return o.Extensions.validFlagsMap(&o) } diff --git a/comid/measurement.go b/comid/measurement.go index b1909d87..9ef993a6 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -247,6 +247,16 @@ func (o *Mval) MarshalCBOR() ([]byte, error) { return encoding.SerializeStructToCBOR(em, o) } +// UnmarshalJSON deserializes from JSON +func (o *Mval) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// MarshalJSON serializes to JSON +func (o *Mval) MarshalJSON() ([]byte, error) { + return encoding.SerializeStructToJSON(o) +} + func (o Mval) Valid() error { if o.Ver == nil && o.SVN == nil && @@ -284,7 +294,7 @@ func (o Mval) Valid() error { // TODO(tho) MAC addr & friends (see https://github.com/veraison/corim/issues/18) - return o.Extensions.ValidMval(&o) + return o.Extensions.validMval(&o) } // Version stores a version-map with JSON and CBOR serializations. diff --git a/comid/measurement_test.go b/comid/measurement_test.go index c9abed17..d5d58bff 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -103,7 +103,7 @@ func TestMeasurement_NewUUIDMeasurement_some_value(t *testing.T) { tv := NewUUIDMeasurement(TestUUID). SetMinSVN(2). - SetOpFlags(OpFlagDebug). + SetFlagsTrue(FlagIsDebug). SetVersion("1.2.3", swid.VersionSchemeSemVer) require.NotNil(t, tv) diff --git a/comid/referencevalue_test.go b/comid/referencevalue_test.go new file mode 100644 index 00000000..00b89d18 --- /dev/null +++ b/comid/referencevalue_test.go @@ -0,0 +1,22 @@ +package comid + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_ReferenceValue(t *testing.T) { + rv := ReferenceValue{} + err := rv.Valid() + assert.EqualError(t, err, "environment validation failed: environment must not be empty") + + rv.Environment.Instance = NewInstance() + id, err := uuid.NewUUID() + require.NoError(t, err) + rv.Environment.Instance.SetUUID(id) + err = rv.Valid() + assert.EqualError(t, err, "measurements validation failed: no measurement entries") +} diff --git a/comid/triples.go b/comid/triples.go index 28df6b75..e3e9ac90 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -3,13 +3,50 @@ package comid -import "fmt" +import ( + "fmt" + + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" +) type Triples struct { ReferenceValues *[]ReferenceValue `cbor:"0,keyasint,omitempty" json:"reference-values,omitempty"` EndorsedValues *[]EndorsedValue `cbor:"1,keyasint,omitempty" json:"endorsed-values,omitempty"` AttestVerifKeys *[]AttestVerifKey `cbor:"2,keyasint,omitempty" json:"attester-verification-keys,omitempty"` DevIdentityKeys *[]DevIdentityKey `cbor:"3,keyasint,omitempty" json:"dev-identity-keys,omitempty"` + + Extensions +} + +// RegisterExtensions registers a struct as a collections of extensions +func (o *Triples) RegisterExtensions(exts extensions.IExtensionsValue) { + o.Extensions.Register(exts) +} + +// GetExtensions returns pervisouosly registered extension +func (o *Triples) GetExtensions() extensions.IExtensionsValue { + return o.Extensions.IExtensionsValue +} + +// UnmarshalCBOR deserializes from CBOR +func (o *Triples) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +// MarshalCBOR serializes to CBOR +func (o *Triples) MarshalCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) +} + +// UnmarshalJSON deserializes from JSON +func (o *Triples) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// MarshalJSON serializes to JSON +func (o *Triples) MarshalJSON() ([]byte, error) { + return encoding.SerializeStructToJSON(o) } // Valid checks that the Triples is valid as per the specification @@ -52,7 +89,7 @@ func (o Triples) Valid() error { } } - return nil + return o.Extensions.validTriples(&o) } func (o *Triples) AddReferenceValue(val ReferenceValue) *Triples { diff --git a/corim/entity.go b/corim/entity.go index 49aa1468..cabea3d3 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -7,6 +7,8 @@ import ( "fmt" "github.com/veraison/corim/comid" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" ) // Entity stores an entity-map capable of CBOR and JSON serializations. @@ -14,12 +16,24 @@ type Entity struct { EntityName string `cbor:"0,keyasint" json:"name"` RegID *comid.TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` Roles Roles `cbor:"2,keyasint" json:"roles"` + + Extensions } func NewEntity() *Entity { return &Entity{} } +// RegisterExtensions registers a struct as a collections of extensions +func (o *Entity) RegisterExtensions(exts extensions.IExtensionsValue) { + o.Extensions.Register(exts) +} + +// GetExtensions returns pervisouosly registered extension +func (o *Entity) GetExtensions() extensions.IExtensionsValue { + return o.Extensions.IExtensionsValue +} + // SetEntityName is used to set the EntityName field of Entity using supplied name func (o *Entity) SetEntityName(name string) *Entity { if o != nil { @@ -72,7 +86,27 @@ func (o Entity) Valid() error { return fmt.Errorf("invalid entity: %w", err) } - return nil + return o.Extensions.validEntity(&o) +} + +// UnmarshalCBOR deserializes from CBOR +func (o *Entity) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +// MarshalCBOR serializes to CBOR +func (o *Entity) MarshalCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) +} + +// UnmarshalJSON deserializes from JSON +func (o *Entity) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// MarshalJSON serializes to JSON +func (o *Entity) MarshalJSON() ([]byte, error) { + return encoding.SerializeStructToJSON(o) } // Entities is an array of entity-map's diff --git a/corim/extensions.go b/corim/extensions.go new file mode 100644 index 00000000..0bd3502f --- /dev/null +++ b/corim/extensions.go @@ -0,0 +1,68 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "github.com/veraison/corim/extensions" +) + +type IEntityConstrainer interface { + ConstrainEntity(*Entity) error +} + +type ICorimConstrainer interface { + ConstrainCorim(*UnsignedCorim) error +} + +type ISignerConstrainer interface { + ConstrainSigner(*Signer) error +} + +type Extensions struct { + extensions.Extensions +} + +func (o *Extensions) validEntity(entity *Entity) error { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(IEntityConstrainer) + if ok { + if err := ev.ConstrainEntity(entity); err != nil { + return err + } + } + + return nil +} + +func (o *Extensions) validCorim(c *UnsignedCorim) error { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(ICorimConstrainer) + if ok { + if err := ev.ConstrainCorim(c); err != nil { + return err + } + } + + return nil +} + +func (o *Extensions) validSigner(signer *Signer) error { + if !o.HaveExtensions() { + return nil + } + + ev, ok := o.IExtensionsValue.(ISignerConstrainer) + if ok { + if err := ev.ConstrainSigner(signer); err != nil { + return err + } + } + + return nil +} diff --git a/corim/extensions_test.go b/corim/extensions_test.go new file mode 100644 index 00000000..42deb71d --- /dev/null +++ b/corim/extensions_test.go @@ -0,0 +1,86 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "errors" + "testing" + + "github.com/fxamacker/cbor/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type TestExtensions struct { + Address string `cbor:"-1,keyasint,omitempty" json:"address,omitempty"` + Size int `cbor:"-2,keyasint,omitempty" json:"size,omitempty"` +} + +func (o TestExtensions) ConstrainEntity(ent *Entity) error { + if ent.EntityName != "Futurama" { + return errors.New(`EntityName must be "Futurama"`) // nolint:golint + } + + return nil +} + +func (o TestExtensions) ConstrainCorim(c *UnsignedCorim) error { + return errors.New("invalid") +} + +func (o TestExtensions) ConstrainSigner(s *Signer) error { + return errors.New("invalid") +} + +func TestEntityExtensions_Valid(t *testing.T) { + ent := NewEntity() + ent.SetEntityName("The Simpsons") + ent.SetRoles(RoleManifestCreator) + + err := ent.Valid() + assert.NoError(t, err) + + ent.RegisterExtensions(&TestExtensions{}) + err = ent.Valid() + assert.EqualError(t, err, `EntityName must be "Futurama"`) + + ent.SetEntityName("Futurama") + err = ent.Valid() + assert.NoError(t, err) + + assert.EqualError(t, ent.Extensions.validCorim(nil), "invalid") + assert.EqualError(t, ent.Extensions.validSigner(nil), "invalid") +} + +func TestEntityExtensions_CBOR(t *testing.T) { + data := []byte{ + 0xa4, // map(4) + + 0x00, // key 0 + 0x64, // val tstr(4) + 0x61, 0x63, 0x6d, 0x65, // "acme" + + 0x02, // key 2 + 0x81, // array(1) + 0x01, // 1 + + 0x20, // key -1 + 0x63, // val tstr(3) + 0x66, 0x6f, 0x6f, // "foo" + + 0x21, // key -2 + 0x06, // val 6 + } + + ent := NewEntity() + ent.RegisterExtensions(&TestExtensions{}) + + err := cbor.Unmarshal(data, &ent) + assert.NoError(t, err) + + assert.Equal(t, ent.EntityName, "acme") + + address, err := ent.Get("address") + require.NoError(t, err) + assert.Equal(t, address, "foo") +} diff --git a/corim/meta.go b/corim/meta.go index 45f1ea49..9ce834e5 100644 --- a/corim/meta.go +++ b/corim/meta.go @@ -10,17 +10,31 @@ import ( "time" "github.com/veraison/corim/comid" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" ) type Signer struct { Name string `cbor:"0,keyasint" json:"name"` URI *comid.TaggedURI `cbor:"1,keyasint,omitempty" json:"uri,omitempty"` + + Extensions } func NewSigner() *Signer { return &Signer{} } +// RegisterExtensions registers a struct as a collections of extensions +func (o *Signer) RegisterExtensions(exts extensions.IExtensionsValue) { + o.Extensions.Register(exts) +} + +// GetExtensions returns previously registered extension +func (o *Signer) GetExtensions() extensions.IExtensionsValue { + return o.Extensions.IExtensionsValue +} + // SetName sets the target Signer's name to the supplied value func (o *Signer) SetName(name string) *Signer { if o != nil { @@ -61,7 +75,27 @@ func (o Signer) Valid() error { } } - return nil + return o.Extensions.validSigner(&o) +} + +// UnmarshalCBOR deserializes from CBOR +func (o *Signer) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +// MarshalCBOR serializes to CBOR +func (o *Signer) MarshalCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) +} + +// UnmarshalJSON deserializes from JSON +func (o *Signer) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// MarshalJSON serializes to JSON +func (o *Signer) MarshalJSON() ([]byte, error) { + return encoding.SerializeStructToJSON(o) } // Meta stores a corim-meta-map with JSON and CBOR serializations. It carries diff --git a/corim/meta_test.go b/corim/meta_test.go index a4dd34cd..811495cf 100644 --- a/corim/meta_test.go +++ b/corim/meta_test.go @@ -183,3 +183,15 @@ func TestMeta_FromCBOR_full(t *testing.T) { assert.Equal(t, notBefore.Unix(), actual.Validity.NotBefore.Unix()) assert.Equal(t, notAfter.Unix(), actual.Validity.NotAfter.Unix()) } + +func Test_Signer_Valid(t *testing.T) { + var signer Signer + + assert.EqualError(t, signer.Valid(), "empty name") + + signer.Name = "test-signer" + uri := comid.TaggedURI("@@@") + signer.URI = &uri + + assert.EqualError(t, signer.Valid(), `invalid URI: "@@@" is not an absolute URI`) +} diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 9e030b3c..eae15d45 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -246,7 +246,7 @@ func TestSignedCorim_FromCOSE_fail_invalid_corim(t *testing.T) { var actual SignedCorim err := actual.FromCOSE(tv) - assert.EqualError(t, err, "failed validation of unsigned CoRIM: tags validation failed: no tags") + assert.EqualError(t, err, `failed CBOR decoding of unsigned CoRIM: missing mandatory field "Tags" (1)`) } func TestSignedCorim_FromCOSE_fail_no_content_type(t *testing.T) { diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 68ca6786..1247527e 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -4,12 +4,13 @@ package corim import ( - "encoding/json" "errors" "fmt" "time" "github.com/veraison/corim/cots" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" "github.com/veraison/corim/comid" "github.com/veraison/eat" @@ -19,12 +20,22 @@ import ( // UnsignedCorim is the top-level representation of the unsigned-corim-map with // CBOR and JSON serialization. type UnsignedCorim struct { - ID swid.TagID `cbor:"0,keyasint" json:"corim-id"` - Tags []Tag `cbor:"1,keyasint" json:"tags"` + ID swid.TagID `cbor:"0,keyasint" json:"corim-id"` + // note: even though tags are mandatory for CoRIM, we allow omitting + // them in our JSON templates for cocli (the min template just has + // corim-id). Since we're never writing JSON (so far), this normally + // wouldn't matter, however the custom serialization code we use to + // handle embedded structs relies on the omitempty entry to determine + // if a fieled is optional, so we use it during unmarshaling as well as + // marshaling. Hence omitempty is present for the json tag, but not + // cbor. + Tags []Tag `cbor:"1,keyasint" json:"tags,omitempty"` DependentRims *[]Locator `cbor:"2,keyasint,omitempty" json:"dependent-rims,omitempty"` Profiles *[]eat.Profile `cbor:"3,keyasint,omitempty" json:"profiles,omitempty"` RimValidity *Validity `cbor:"4,keyasint,omitempty" json:"validity,omitempty"` Entities *Entities `cbor:"5,keyasint,omitempty" json:"entities,omitempty"` + + Extensions } // NewUnsignedCorim instantiates an empty UnsignedCorim @@ -32,6 +43,16 @@ func NewUnsignedCorim() *UnsignedCorim { return &UnsignedCorim{} } +// RegisterExtensions registers a struct as a collections of extensions +func (o *UnsignedCorim) RegisterExtensions(exts extensions.IExtensionsValue) { + o.Extensions.Register(exts) +} + +// GetExtensions returns pervisouosly registered extension +func (o *UnsignedCorim) GetExtensions() extensions.IExtensionsValue { + return o.Extensions.IExtensionsValue +} + // SetID sets the corim-id in the unsigned-corim-map to the supplied value. The // corim-id can be passed as UUID in string or binary form (i.e., byte array), // or as a (non-empty) string @@ -239,22 +260,22 @@ func (o UnsignedCorim) Valid() error { } } - return nil + return o.Extensions.validCorim(&o) } // ToCBOR serializes the target unsigned CoRIM to CBOR -func (o UnsignedCorim) ToCBOR() ([]byte, error) { - return em.Marshal(&o) +func (o *UnsignedCorim) ToCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) } // FromCBOR deserializes a CBOR-encoded unsigned CoRIM into the target UnsignedCorim func (o *UnsignedCorim) FromCBOR(data []byte) error { - return dm.Unmarshal(data, o) + return encoding.PopulateStructFromCBOR(dm, data, o) } // FromJSON deserializes a JSON-encoded unsigned CoRIM into the target UnsignedCorim func (o *UnsignedCorim) FromJSON(data []byte) error { - return json.Unmarshal(data, o) + return encoding.PopulateStructFromJSON(data, o) } // Tag is either a CBOR-encoded CoMID, CoSWID or CoTS diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index d9b4d105..c2b7ee86 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -299,3 +299,11 @@ func TestUnsignedCorim_AddEntity_non_nil_empty_URI(t *testing.T) { assert.Nil(t, tv) } + +func TestUnsignedCorim_FromJSON(t *testing.T) { + data := []byte(`{"corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc"}`) + + err := NewUnsignedCorim().FromJSON(data) + + assert.NoError(t, err) +} diff --git a/encoding/cbor.go b/encoding/cbor.go new file mode 100644 index 00000000..5d7a3533 --- /dev/null +++ b/encoding/cbor.go @@ -0,0 +1,409 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package encoding + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "reflect" + "strconv" + "strings" + + cbor "github.com/fxamacker/cbor/v2" +) + +func SerializeStructToCBOR(em cbor.EncMode, source any) ([]byte, error) { + rawMap := newStructFieldsCBOR() + + structType := reflect.TypeOf(source) + structVal := reflect.ValueOf(source) + + if err := doSerializeStructToCBOR(em, rawMap, structType, structVal); err != nil { + return nil, err + } + + return rawMap.ToCBOR(em) +} + +func doSerializeStructToCBOR( + em cbor.EncMode, + rawMap *structFieldsCBOR, + structType reflect.Type, + structVal reflect.Value, +) error { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + structVal = structVal.Elem() + } + + var embeds []embedded + + for i := 0; i < structVal.NumField(); i++ { + typeField := structType.Field(i) + valField := structVal.Field(i) + + if collectEmbedded(&typeField, valField, &embeds) { + continue + } + + tag, ok := typeField.Tag.Lookup("cbor") + if !ok { + continue + } + + parts := strings.Split(tag, ",") + keyString := parts[0] + + isOmitEmpty := false + if len(parts) > 1 { + for _, option := range parts[1:] { + if option == omitempty { + isOmitEmpty = true + break + } + } + } + + // do not serialize zero values if the corresponding field is + // omitempty + if isOmitEmpty && valField.IsZero() { + continue + } + + keyInt, err := strconv.Atoi(keyString) + if err != nil { + return fmt.Errorf("non-integer cbor key: %s", keyString) + } + + data, err := em.Marshal(valField.Interface()) + if err != nil { + return fmt.Errorf("error marshaling field %q: %w", + typeField.Name, + err, + ) + } + + if err := rawMap.Add(keyInt, cbor.RawMessage(data)); err != nil { + return err + } + } + + for _, emb := range embeds { + if err := doSerializeStructToCBOR(em, rawMap, emb.Type, emb.Value); err != nil { + return err + } + } + + return nil +} + +func PopulateStructFromCBOR(dm cbor.DecMode, data []byte, dest any) error { + rawMap := newStructFieldsCBOR() + + if err := rawMap.FromCBOR(dm, data); err != nil { + return err + } + + structType := reflect.TypeOf(dest) + structVal := reflect.ValueOf(dest) + + return doPopulateStructFromCBOR(dm, rawMap, structType, structVal) +} + +func doPopulateStructFromCBOR( + dm cbor.DecMode, + rawMap *structFieldsCBOR, + structType reflect.Type, + structVal reflect.Value, +) error { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + structVal = structVal.Elem() + } + + var embeds []embedded + + for i := 0; i < structVal.NumField(); i++ { + typeField := structType.Field(i) + valField := structVal.Field(i) + + if collectEmbedded(&typeField, valField, &embeds) { + continue + } + + tag, ok := typeField.Tag.Lookup("cbor") + if !ok { + continue + } + + parts := strings.Split(tag, ",") + keyString := parts[0] + + isOmitEmpty := false + if len(parts) > 1 { + for _, option := range parts[1:] { + if option == omitempty { + isOmitEmpty = true + break + } + } + } + + keyInt, err := strconv.Atoi(keyString) + if err != nil { + return fmt.Errorf("non-integer cbor key %s", keyString) + } + + rawVal, ok := rawMap.Get(keyInt) + if !ok { + if isOmitEmpty { + continue + } + + return fmt.Errorf("missing mandatory field %q (%d)", + typeField.Name, keyInt) + } + + fieldPtr := valField.Addr().Interface() + if err := dm.Unmarshal(rawVal, fieldPtr); err != nil { + return fmt.Errorf("error unmarshalling field %q: %w", + typeField.Name, + err, + ) + } + + rawMap.Delete(keyInt) + } + + for _, emb := range embeds { + if err := doPopulateStructFromCBOR(dm, rawMap, emb.Type, emb.Value); err != nil { + return err + } + } + + return nil +} + +// structFieldsCBOR is a specialized implementation of "OrderedMap", where the +// order of the keys is kept track of, and used when serializing the map to +// CBOR. While CBOR maps do not mandate any particular ordering, and so this +// isn't strictly necessary, it is useful to have a _stable_ serialization +// order for map keys to be compatible with regular Go struct serialization +// behavior. This is also useful for tests/examples that compare encoded +// []byte's. +type structFieldsCBOR struct { + Fields map[int]cbor.RawMessage + Keys []int +} + +func newStructFieldsCBOR() *structFieldsCBOR { + return &structFieldsCBOR{ + Fields: make(map[int]cbor.RawMessage), + } +} + +func (o structFieldsCBOR) Has(key int) bool { + _, ok := o.Fields[key] + return ok +} + +func (o *structFieldsCBOR) Add(key int, val cbor.RawMessage) error { + if o.Has(key) { + return fmt.Errorf("duplicate cbor key: %d", key) + } + + o.Fields[key] = val + o.Keys = append(o.Keys, key) + + return nil +} + +func (o *structFieldsCBOR) Get(key int) (cbor.RawMessage, bool) { + val, ok := o.Fields[key] + return val, ok +} + +func (o *structFieldsCBOR) Delete(key int) { + delete(o.Fields, key) + + for i, existing := range o.Keys { + if existing == key { + o.Keys = append(o.Keys[:i], o.Keys[i+1:]...) + } + } +} + +func (o *structFieldsCBOR) ToCBOR(em cbor.EncMode) ([]byte, error) { + var out []byte + + header := byte(0xa0) // 0b101_00000 -- Major Type 5 == map + mapLen := len(o.Keys) + if mapLen == 0 { + return []byte{header}, nil + } else if mapLen < 24 { + header = header | byte(mapLen) + out = append(out, header) + } else if mapLen <= math.MaxUint8 { + header = header | byte(24) + out = append(out, header, uint8(mapLen)) + } else if mapLen <= math.MaxUint16 { + header = header | byte(25) + out = append(out, header) + out = binary.BigEndian.AppendUint16(out, uint16(mapLen)) + } else { + header = header | byte(26) + out = append(out, header) + out = binary.BigEndian.AppendUint32(out, uint32(mapLen)) + } + // Since len() returns an int, the value cannot exceed MaxUint32, so + // the 8-byte length variant cannot occur. + + for _, key := range o.Keys { + marshalledKey, err := em.Marshal(key) + if err != nil { + return nil, fmt.Errorf("problem marshaling key %d: %w", key, err) + } + + out = append(out, marshalledKey...) + out = append(out, o.Fields[key]...) + } + + return out, nil +} + +func (o *structFieldsCBOR) FromCBOR(dm cbor.DecMode, data []byte) error { + if len(data) == 0 { + return errors.New("empty input") + } + + header := data[0] + rest := data[1:] + additionalInfo := 0x1f & header + + var err error + + majorType := (0xe0 & header) >> 5 + if majorType == 6 { // tag + _, rest, err = processAdditionalInfo(additionalInfo, rest) + if err != nil { + return err + } + + header = rest[0] + rest = rest[1:] + majorType = (0xe0 & header) >> 5 + additionalInfo = 0x1f & header + } + + if majorType != 5 { + return fmt.Errorf("expected map (CBOR Major Type 5), found Major Type %d", majorType) + } + + var mapLen int + + mapLen, rest, err = processAdditionalInfo(additionalInfo, rest) + if err != nil { + return err + } + + if mapLen != 0 { + o.Fields = make(map[int]cbor.RawMessage, mapLen) + + for i := 0; i < mapLen; i++ { + rest, err = o.unmarshalKeyValue(dm, rest) + if err != nil { + return fmt.Errorf("map item %d: %w", i, err) + } + } + } else { // mapLen == 0 --> indefinite encoding + o.Fields = make(map[int]cbor.RawMessage) + + i := 0 + done := false + for len(rest) > 0 { + if rest[0] == 0xFF { + done = true + break + } + + rest, err = o.unmarshalKeyValue(dm, rest) + if err != nil { + return fmt.Errorf("map item %d: %w", i, err) + } + + i++ + } + + if !done { + return errors.New("unexpected EOF") + } + } + + return nil +} + +func (o *structFieldsCBOR) unmarshalKeyValue(dm cbor.DecMode, rest []byte) ([]byte, error) { + var key int + var val cbor.RawMessage + var err error + + rest, err = dm.UnmarshalFirst(rest, &key) + if err != nil { + return rest, fmt.Errorf("could not unmarshal key: %w", err) + } + + rest, err = dm.UnmarshalFirst(rest, &val) + if err != nil { + return rest, fmt.Errorf("could not unmarshal value: %w", err) + } + + if err = o.Add(key, val); err != nil { + return rest, err + } + + return rest, nil +} + +func processAdditionalInfo( + additionalInfo byte, + data []byte, +) (int, []byte, error) { + var val int + rest := data + + if additionalInfo < 24 { + val = int(additionalInfo) + } else if additionalInfo < 28 { + switch additionalInfo - 23 { + case 1: + if len(data) < 1 { + return 0, nil, errors.New("unexpected EOF") + } + val = int(data[0]) + rest = data[1:] + case 2: + if len(data) < 2 { + return 0, nil, errors.New("unexpected EOF") + } + val = int(binary.BigEndian.Uint16(data[:2])) + rest = data[2:] + case 3: + if len(data) < 4 { + return 0, nil, errors.New("unexpected EOF") + } + val = int(binary.BigEndian.Uint32(data[:4])) + rest = data[4:] + default: + return 0, nil, errors.New("cbor: cannot decode length value of 8 bytes") + } + } else if additionalInfo == 31 { + val = 0 // indefinite encoding + } else { + return 0, nil, fmt.Errorf("cbor: unexpected additional information value %d", additionalInfo) + } + + return val, rest, nil +} diff --git a/encoding/cbor_test.go b/encoding/cbor_test.go new file mode 100644 index 00000000..1dda9146 --- /dev/null +++ b/encoding/cbor_test.go @@ -0,0 +1,279 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package encoding + +import ( + "testing" + + "github.com/fxamacker/cbor/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_PopulateStructFromCBOR_simple(t *testing.T) { + type SimpleStruct struct { + FieldOne string `cbor:"0,keyasint,omitempty"` + FieldTwo int `cbor:"1,keyasint"` + } + + var v SimpleStruct + + data := []byte{ + 0xa2, // map(2) + + 0x00, // key 0 + 0x64, // val tstr(4) + 0x61, 0x63, 0x6d, 0x65, // "acme" + + 0x01, // key 1 + 0x06, // val 6 + } + + dm, err := cbor.DecOptions{}.DecMode() + require.NoError(t, err) + + err = PopulateStructFromCBOR(dm, data, &v) + require.NoError(t, err) + assert.Equal(t, "acme", v.FieldOne) + assert.Equal(t, 6, v.FieldTwo) + + data = []byte{ + 0xa1, // map(1) + + 0x01, // key 1 + 0x06, // val 6 + } + v = SimpleStruct{} + + err = PopulateStructFromCBOR(dm, data, &v) + require.NoError(t, err) + assert.Equal(t, "", v.FieldOne) + assert.Equal(t, 6, v.FieldTwo) + + data = []byte{ + 0xa1, // map(1) + + 0x02, // key 2 + 0x64, // val tstr(4) + 0x61, 0x63, 0x6d, 0x65, // "acme" + } + v = SimpleStruct{} + + err = PopulateStructFromCBOR(dm, data, &v) + assert.EqualError(t, err, `missing mandatory field "FieldTwo" (1)`) + + err = PopulateStructFromCBOR(dm, []byte{0x01}, &v) + assert.EqualError(t, err, `expected map (CBOR Major Type 5), found Major Type 0`) + + type CompositeStruct struct { + FieldThree string `cbor:"2,keyasint"` + SimpleStruct + } + + var c CompositeStruct + + data = []byte{ + 0xa3, // map(3) + + 0x00, // key 0 + 0x64, // val tstr(4) + 0x61, 0x63, 0x6d, 0x65, // "acme" + + 0x01, // key 1 + 0x06, // val 6 + + 0x02, // key 2 + 0x63, // val tstr(3) + 0x66, 0x6f, 0x6f, // "foo" + } + + err = PopulateStructFromCBOR(dm, data, &c) + require.NoError(t, err) + assert.Equal(t, "acme", c.FieldOne) + assert.Equal(t, 6, c.FieldTwo) + assert.Equal(t, "foo", c.FieldThree) + + em, err := cbor.EncOptions{}.EncMode() + require.NoError(t, err) + + res, err := SerializeStructToCBOR(em, &c) + require.NoError(t, err) + + var c2 CompositeStruct + err = PopulateStructFromCBOR(dm, res, &c2) + require.NoError(t, err) + assert.EqualValues(t, c, c2) + +} + +func Test_structFieldsCBOR_CRUD(t *testing.T) { + sf := newStructFieldsCBOR() + + err := sf.Add(2, cbor.RawMessage{0x02}) + assert.NoError(t, err) + + err = sf.Add(1, cbor.RawMessage{0x01}) + assert.NoError(t, err) + + err = sf.Add(3, cbor.RawMessage{0x03}) + assert.NoError(t, err) + + assert.Equal(t, []int{2, 1, 3}, sf.Keys) + assert.True(t, sf.Has(3)) + assert.False(t, sf.Has(4)) + + val, ok := sf.Get(2) + assert.True(t, ok) + assert.Equal(t, cbor.RawMessage{0x2}, val) + + _, ok = sf.Get(4) + assert.False(t, ok) + + sf.Delete(2) + _, ok = sf.Get(2) + assert.False(t, ok) + + err = sf.Add(1, cbor.RawMessage{0x11}) + assert.EqualError(t, err, "duplicate cbor key: 1") +} + +func Test_structFieldsCBOR_CBOR_roundtrip(t *testing.T) { + em, err := cbor.EncOptions{}.EncMode() + require.NoError(t, err) + dm, err := cbor.DecOptions{}.DecMode() + require.NoError(t, err) + + sf := newStructFieldsCBOR() + + data, err := sf.ToCBOR(em) + require.NoError(t, err) + assert.Equal(t, data, []byte{0xa0}) // empty map + + for i := 0; i < 5; i++ { + err = sf.Add(i, cbor.RawMessage{0x00}) + require.NoError(t, err) + } + + data, err = sf.ToCBOR(em) + require.NoError(t, err) + assert.Equal(t, data, []byte{ + 0xa5, // map 5 + 0x00, 0x00, + 0x01, 0x00, + 0x02, 0x00, + 0x03, 0x00, + 0x04, 0x00, + }) + + sfOut := newStructFieldsCBOR() + err = sfOut.FromCBOR(dm, data) + require.NoError(t, err) + assert.Equal(t, sf, sfOut) + + for i := 5; i < 200; i++ { + err = sf.Add(i, cbor.RawMessage{0x00}) + require.NoError(t, err) + } + + data, err = sf.ToCBOR(em) + require.NoError(t, err) + assert.Equal(t, data[:2], []byte{ + 0xb8, 0xc8, // map 200 + }) + + sfOut = newStructFieldsCBOR() + err = sfOut.FromCBOR(dm, data) + require.NoError(t, err) + assert.Equal(t, sf, sfOut) + + for i := 200; i < 2048; i++ { + err = sf.Add(i, cbor.RawMessage{0x00}) + require.NoError(t, err) + } + + data, err = sf.ToCBOR(em) + require.NoError(t, err) + assert.Equal(t, data[:3], []byte{ + 0xb9, 0x08, 0x00, // map 2048 + }) + + sfOut = newStructFieldsCBOR() + err = sfOut.FromCBOR(dm, data) + require.NoError(t, err) + assert.Equal(t, sf, sfOut) +} + +func Test_structFieldsCBOR_CBOR_decode_tagged(t *testing.T) { + data := []byte{ + 0xc1, // tag 1 + 0xa5, // map 5 + 0x00, 0x00, + 0x01, 0x00, + 0x02, 0x00, + 0x03, 0x00, + 0x04, 0x00, + } + + dm, err := cbor.DecOptions{}.DecMode() + require.NoError(t, err) + + sfOut := newStructFieldsCBOR() + err = sfOut.FromCBOR(dm, data) + require.NoError(t, err) + assert.Equal(t, []int{0, 1, 2, 3, 4}, sfOut.Keys) +} + +func Test_structFieldsCBOR_CBOR_decode_indefinite(t *testing.T) { + data := []byte{ + 0xbf, // indefinite map + 0x00, 0x00, + 0x01, 0x00, + 0x02, 0x00, + 0x03, 0x00, + 0x04, 0x00, + 0xff, // break + } + + dm, err := cbor.DecOptions{}.DecMode() + require.NoError(t, err) + + sfOut := newStructFieldsCBOR() + err = sfOut.FromCBOR(dm, data) + require.NoError(t, err) + assert.Equal(t, []int{0, 1, 2, 3, 4}, sfOut.Keys) +} + +func Test_structFieldsCBOR_CBOR_decode_negative(t *testing.T) { + dm, err := cbor.DecOptions{}.DecMode() + require.NoError(t, err) + + sfOut := newStructFieldsCBOR() + err = sfOut.FromCBOR(dm, []byte{0xa1, 0xff, 0x00}) + assert.EqualError(t, err, `map item 0: could not unmarshal key: cbor: unexpected "break" code`) + err = sfOut.FromCBOR(dm, []byte{0xbf, 0x00, 0x00}) + assert.EqualError(t, err, `unexpected EOF`) + err = sfOut.FromCBOR(dm, []byte{0xa1, 0x00, 0xff}) + assert.EqualError(t, err, `map item 0: could not unmarshal value: cbor: unexpected "break" code`) + + err = sfOut.FromCBOR(dm, []byte{0x00}) + assert.EqualError(t, err, `expected map (CBOR Major Type 5), found Major Type 0`) +} + +func Test_processAdditionalInfo(t *testing.T) { + addInfo := byte(26) + data := []byte{0x00, 0x00, 0x00, 0x01} + + val, rest, err := processAdditionalInfo(addInfo, data) + require.NoError(t, err) + assert.Equal(t, 1, val) + assert.Equal(t, []byte{}, rest) + + _, _, err = processAdditionalInfo(byte(27), data) + assert.EqualError(t, err, "cbor: cannot decode length value of 8 bytes") + + _, _, err = processAdditionalInfo(byte(28), data) + assert.EqualError(t, err, "cbor: unexpected additional information value 28") + + _, _, err = processAdditionalInfo(addInfo, []byte{}) + assert.EqualError(t, err, "unexpected EOF") +} diff --git a/encoding/embedded.go b/encoding/embedded.go new file mode 100644 index 00000000..1bfeb999 --- /dev/null +++ b/encoding/embedded.go @@ -0,0 +1,49 @@ +package encoding + +import "reflect" + +const omitempty = "omitempty" + +type embedded struct { + Type reflect.Type + Value reflect.Value +} + +// collectEmbedded returns true if the Field is embedded (regardless of +// whether or not it was collected). +func collectEmbedded( + typeField *reflect.StructField, + valField reflect.Value, + embeds *[]embedded, +) bool { + // embedded fields are alway anonymous:w + if !typeField.Anonymous { + return false + } + + if typeField.Name == typeField.Type.Name() && + (typeField.Type.Kind() == reflect.Struct || + typeField.Type.Kind() == reflect.Interface) { + + var fieldType reflect.Type + var fieldValue reflect.Value + + if typeField.Type.Kind() == reflect.Interface { + fieldValue = valField.Elem() + if fieldValue.Kind() == reflect.Invalid { + // no value underlying the interface + return true + } + // use the interface's underlying value's real type + fieldType = valField.Elem().Type() + } else { + fieldType = typeField.Type + fieldValue = valField + } + + *embeds = append(*embeds, embedded{Type: fieldType, Value: fieldValue}) + return true + } + + return false +} diff --git a/encoding/json.go b/encoding/json.go new file mode 100644 index 00000000..e8193e4b --- /dev/null +++ b/encoding/json.go @@ -0,0 +1,319 @@ +package encoding + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" +) + +func SerializeStructToJSON(source any) ([]byte, error) { + rawMap := newStructFieldsJSON() + + structType := reflect.TypeOf(source) + structVal := reflect.ValueOf(source) + + if err := doSerializeStructToJSON(rawMap, structType, structVal); err != nil { + return nil, err + } + + return rawMap.ToJSON() +} + +func doSerializeStructToJSON( + rawMap *structFieldsJSON, + structType reflect.Type, + structVal reflect.Value, +) error { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + structVal = structVal.Elem() + } + + var embeds []embedded + + for i := 0; i < structVal.NumField(); i++ { + typeField := structType.Field(i) + valField := structVal.Field(i) + + if collectEmbedded(&typeField, valField, &embeds) { + continue + } + + tag, ok := typeField.Tag.Lookup("json") + if !ok { + continue + } + + parts := strings.Split(tag, ",") + key := parts[0] + + isOmitEmpty := false + if len(parts) > 1 { + for _, option := range parts[1:] { + if option == omitempty { + isOmitEmpty = true + break + } + } + } + + // do not serialize zero values if the corresponding field is + // omitempty + if isOmitEmpty && valField.IsZero() { + continue + } + + data, err := json.Marshal(valField.Interface()) + if err != nil { + return fmt.Errorf("error marshaling field %q: %w", + typeField.Name, + err, + ) + } + + if err := rawMap.Add(key, json.RawMessage(data)); err != nil { + return err + } + } + + for _, emb := range embeds { + if err := doSerializeStructToJSON(rawMap, emb.Type, emb.Value); err != nil { + return err + } + } + + return nil +} + +func PopulateStructFromJSON(data []byte, dest any) error { + rawMap := newStructFieldsJSON() + + if err := rawMap.FromJSON(data); err != nil { + return err + } + + structType := reflect.TypeOf(dest) + structVal := reflect.ValueOf(dest) + + return doPopulateStructFromJSON(rawMap, structType, structVal) +} + +func doPopulateStructFromJSON( + rawMap *structFieldsJSON, + structType reflect.Type, + structVal reflect.Value, +) error { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + structVal = structVal.Elem() + } + + var embeds []embedded + + for i := 0; i < structVal.NumField(); i++ { + typeField := structType.Field(i) + valField := structVal.Field(i) + + if collectEmbedded(&typeField, valField, &embeds) { + continue + } + + tag, ok := typeField.Tag.Lookup("json") + if !ok { + continue + } + + parts := strings.Split(tag, ",") + key := parts[0] + + isOmitEmpty := false + if len(parts) > 1 { + for _, option := range parts[1:] { + if option == omitempty { + isOmitEmpty = true + break + } + } + } + + rawVal, ok := rawMap.Get(key) + if !ok { + if isOmitEmpty { + continue + } + + return fmt.Errorf("missing mandatory field %q (%q)", + typeField.Name, key) + } + + fieldPtr := valField.Addr().Interface() + if err := json.Unmarshal(rawVal, fieldPtr); err != nil { + return fmt.Errorf("error unmarshalling field %q: %w", + typeField.Name, + err, + ) + } + + rawMap.Delete(key) + } + + for _, emb := range embeds { + if err := doPopulateStructFromJSON(rawMap, emb.Type, emb.Value); err != nil { + return err + } + } + + return nil +} + +// structFieldsJSON is a specialized implementation of "OrderedMap", where the +// order of the keys is kept track of, and used when serializing the map to +// JSON. While JSON maps do not mandate any particular ordering, and so this +// isn't strictly necessary, it is useful to have a _stable_ serialization +// order for map keys to be compatible with regular Go struct serialization +// behavior. This is also useful for tests/examples that compare encoded +// []byte's. +type structFieldsJSON struct { + Fields map[string]json.RawMessage + Keys []string +} + +func newStructFieldsJSON() *structFieldsJSON { + return &structFieldsJSON{ + Fields: make(map[string]json.RawMessage), + } +} + +func (o structFieldsJSON) Has(key string) bool { + _, ok := o.Fields[key] + return ok +} + +func (o *structFieldsJSON) Add(key string, val json.RawMessage) error { + if o.Has(key) { + return fmt.Errorf("duplicate JSON key: %q", key) + } + + o.Fields[key] = val + o.Keys = append(o.Keys, key) + + return nil +} + +func (o *structFieldsJSON) Get(key string) (json.RawMessage, bool) { + val, ok := o.Fields[key] + return val, ok +} + +func (o *structFieldsJSON) Delete(key string) { + delete(o.Fields, key) + + for i, existing := range o.Keys { + if existing == key { + o.Keys = append(o.Keys[:i], o.Keys[i+1:]...) + } + } +} + +func (o *structFieldsJSON) ToJSON() ([]byte, error) { + var out bytes.Buffer + + out.Write([]byte("{")) + + first := true + for _, key := range o.Keys { + if first { + first = false + } else { + out.Write([]byte(",")) + } + marshaledKey, err := json.Marshal(key) + if err != nil { + return nil, fmt.Errorf("problem marshaling key %s: %w", key, err) + } + out.Write(marshaledKey) + out.Write([]byte(":")) + out.Write(o.Fields[key]) + } + + out.Write([]byte("}")) + + return out.Bytes(), nil +} + +func (o *structFieldsJSON) FromJSON(data []byte) error { + if err := json.Unmarshal(data, &o.Fields); err != nil { + return err + } + + return o.unmarshalKeys(data) +} + +func (o *structFieldsJSON) unmarshalKeys(data []byte) error { + + decoder := json.NewDecoder(bytes.NewReader(data)) + + token, err := decoder.Token() + if err != nil { + return err + } + + if token != json.Delim('{') { + return errors.New("expected start of object") + } + + var keys []string + + for { + token, err = decoder.Token() + if err != nil { + return err + } + + if token == json.Delim('}') { + break + } + + key, ok := token.(string) + if !ok { + return fmt.Errorf("expected string, found %T", token) + } + + keys = append(keys, key) + + if err := skipValue(decoder); err != nil { + return err + } + } + + o.Keys = keys + + return nil +} + +var errEndOfStream = errors.New("invalid end of array or object") + +func skipValue(decoder *json.Decoder) error { + + token, err := decoder.Token() + if err != nil { + return err + } + switch token { + case json.Delim('['), json.Delim('{'): + for { + if err := skipValue(decoder); err != nil { + if err == errEndOfStream { + break + } + return err + } + } + case json.Delim(']'), json.Delim('}'): + return errEndOfStream + } + return nil +} diff --git a/encoding/json_test.go b/encoding/json_test.go new file mode 100644 index 00000000..132d1587 --- /dev/null +++ b/encoding/json_test.go @@ -0,0 +1,121 @@ +package encoding + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_PopulateStructFromJSON(t *testing.T) { + type SimpleStruct struct { + FieldOne string `json:"field-one,omitempty"` + FieldTwo int `json:"field-two"` + } + + var v SimpleStruct + + data := []byte(`{"field-one": "acme", "field-two": 6}`) + + err := PopulateStructFromJSON(data, &v) + require.NoError(t, err) + assert.Equal(t, "acme", v.FieldOne) + assert.Equal(t, 6, v.FieldTwo) + + data = []byte(`{"field-two": 6}`) + v = SimpleStruct{} + + err = PopulateStructFromJSON(data, &v) + require.NoError(t, err) + assert.Equal(t, "", v.FieldOne) + assert.Equal(t, 6, v.FieldTwo) + + data = []byte(`{"field-one": "acme"}`) + v = SimpleStruct{} + + err = PopulateStructFromJSON(data, &v) + assert.EqualError(t, err, `missing mandatory field "FieldTwo" ("field-two")`) + + err = PopulateStructFromJSON([]byte("7"), &v) + assert.EqualError(t, err, `json: cannot unmarshal number into Go value of type map[string]json.RawMessage`) + + type CompositeStruct struct { + FieldThree string `json:"field-three"` + SimpleStruct + } + + var c CompositeStruct + + data = []byte(`{"field-one": "acme", "field-two": 6, "field-three": "foo"}`) + + err = PopulateStructFromJSON(data, &c) + require.NoError(t, err) + assert.Equal(t, "acme", c.FieldOne) + assert.Equal(t, 6, c.FieldTwo) + assert.Equal(t, "foo", c.FieldThree) + + res, err := SerializeStructToJSON(&c) + require.NoError(t, err) + + var c2 CompositeStruct + err = PopulateStructFromJSON(res, &c2) + require.NoError(t, err) + assert.EqualValues(t, c, c2) +} + +func Test_structFieldsJSON_CRUD(t *testing.T) { + sf := newStructFieldsJSON() + + err := sf.Add("two", json.RawMessage("2")) + assert.NoError(t, err) + + err = sf.Add("one", json.RawMessage("1")) + assert.NoError(t, err) + + err = sf.Add("three", json.RawMessage("3")) + assert.NoError(t, err) + + assert.Equal(t, []string{"two", "one", "three"}, sf.Keys) + assert.True(t, sf.Has("three")) + assert.False(t, sf.Has("four")) + + val, ok := sf.Get("two") + assert.True(t, ok) + assert.Equal(t, json.RawMessage("2"), val) + + _, ok = sf.Get("four") + assert.False(t, ok) + + sf.Delete("two") + _, ok = sf.Get("two") + assert.False(t, ok) + + err = sf.Add("one", json.RawMessage("4")) + assert.EqualError(t, err, `duplicate JSON key: "one"`) +} + +func Test_skipValue(t *testing.T) { + text := "" + decoder := json.NewDecoder(strings.NewReader(text)) + err := skipValue(decoder) + assert.EqualError(t, err, "EOF") + + text = "[]" + decoder = json.NewDecoder(strings.NewReader(text)) + _, _ = decoder.Token() // skip the '[' + err = skipValue(decoder) + assert.EqualError(t, err, "invalid end of array or object") + + text = `{"embed": {"one": 1, "two": [1,2,3]}, "other": 1}` + decoder = json.NewDecoder(strings.NewReader(text)) + _, _ = decoder.Token() // skip the '{' + _, _ = decoder.Token() // skip the '"embed"' + err = skipValue(decoder) + assert.NoError(t, err) + + token, err := decoder.Token() + assert.NoError(t, err) + assert.Equal(t, "other", token) +} diff --git a/extensions/extensions.go b/extensions/extensions.go new file mode 100644 index 00000000..4be60fd1 --- /dev/null +++ b/extensions/extensions.go @@ -0,0 +1,379 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package extensions + +import ( + "errors" + "fmt" + "reflect" + "strings" + + "github.com/spf13/cast" +) + +var ErrExtensionNotFound = errors.New("extension not found") + +type IExtensionsValue any + +type Extensions struct { + IExtensionsValue `json:"extensions,omitempty"` +} + +func (o *Extensions) Register(exts IExtensionsValue) { + if reflect.TypeOf(exts).Kind() != reflect.Pointer { + panic("attempting to register a non-pointer IExtensionsValue") + } + + o.IExtensionsValue = exts +} + +func (o *Extensions) HaveExtensions() bool { + return o.IExtensionsValue != nil +} + +func (o *Extensions) Get(name string) (any, error) { + if o.IExtensionsValue == nil { + return nil, fmt.Errorf("%w: %s", ErrExtensionNotFound, name) + } + + extType := reflect.TypeOf(o.IExtensionsValue) + extVal := reflect.ValueOf(o.IExtensionsValue) + if extType.Kind() == reflect.Pointer { + extType = extType.Elem() + extVal = extVal.Elem() + } + + var fieldName, fieldJSONTag, fieldCBORTag string + for i := 0; i < extVal.NumField(); i++ { + typeField := extType.Field(i) + fieldName = typeField.Name + + tag, ok := typeField.Tag.Lookup("json") + if ok { + fieldJSONTag = strings.Split(tag, ",")[0] + } + + tag, ok = typeField.Tag.Lookup("cbor") + if ok { + fieldCBORTag = strings.Split(tag, ",")[0] + } + + if fieldName == name || fieldJSONTag == name || fieldCBORTag == name { + return extVal.Field(i).Interface(), nil + } + } + + return nil, fmt.Errorf("%w: %s", ErrExtensionNotFound, name) +} + +func (o *Extensions) MustGetString(name string) string { + v, _ := o.GetString(name) + return v +} + +func (o *Extensions) GetString(name string) (string, error) { + v, err := o.Get(name) + if err != nil { + return "", err + } + + return cast.ToStringE(v) +} + +func (o *Extensions) MustGetInt(name string) int { + v, _ := o.GetInt(name) + return v +} + +func (o *Extensions) GetInt(name string) (int, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToIntE(v) +} + +func (o *Extensions) MustGetInt64(name string) int64 { + v, _ := o.GetInt64(name) + return v +} + +func (o *Extensions) GetInt64(name string) (int64, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToInt64E(v) +} + +func (o *Extensions) MustGetInt32(name string) int32 { + v, _ := o.GetInt32(name) + return v +} + +func (o *Extensions) GetInt32(name string) (int32, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToInt32E(v) +} + +func (o *Extensions) MustGetInt16(name string) int16 { + v, _ := o.GetInt16(name) + return v +} + +func (o *Extensions) GetInt16(name string) (int16, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToInt16E(v) +} + +func (o *Extensions) MustGetInt8(name string) int8 { + v, _ := o.GetInt8(name) + return v +} + +func (o *Extensions) GetInt8(name string) (int8, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToInt8E(v) +} + +func (o *Extensions) MustGetUint(name string) uint { + v, _ := o.GetUint(name) + return v +} + +func (o *Extensions) GetUint(name string) (uint, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToUintE(v) +} + +func (o *Extensions) MustGetUint64(name string) uint64 { + v, _ := o.GetUint64(name) + return v +} + +func (o *Extensions) GetUint64(name string) (uint64, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToUint64E(v) +} + +func (o *Extensions) MustGetUint32(name string) uint32 { + v, _ := o.GetUint32(name) + return v +} + +func (o *Extensions) GetUint32(name string) (uint32, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToUint32E(v) +} + +func (o *Extensions) MustGetUint16(name string) uint16 { + v, _ := o.GetUint16(name) + return v +} + +func (o *Extensions) GetUint16(name string) (uint16, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToUint16E(v) +} + +func (o *Extensions) MustGetUint8(name string) uint8 { + v, _ := o.GetUint8(name) + return v +} + +func (o *Extensions) GetUint8(name string) (uint8, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToUint8E(v) +} + +func (o *Extensions) MustGetFloat32(name string) float32 { + v, _ := o.GetFloat32(name) + return v +} + +func (o *Extensions) GetFloat32(name string) (float32, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToFloat32E(v) +} + +func (o *Extensions) MustGetFloat64(name string) float64 { + v, _ := o.GetFloat64(name) + return v +} + +func (o *Extensions) GetFloat64(name string) (float64, error) { + v, err := o.Get(name) + if err != nil { + return 0, err + } + + return cast.ToFloat64E(v) +} + +func (o *Extensions) MustGetBool(name string) bool { + v, _ := o.GetBool(name) + return v +} + +func (o *Extensions) GetBool(name string) (bool, error) { + v, err := o.Get(name) + if err != nil { + return false, err + } + + return cast.ToBoolE(v) +} + +func (o *Extensions) MustGetSlice(name string) []any { + v, _ := o.GetSlice(name) + return v +} + +func (o *Extensions) GetSlice(name string) ([]any, error) { + v, err := o.Get(name) + if err != nil { + return []any{}, err + } + + return cast.ToSliceE(v) +} + +func (o *Extensions) MustGetIntSlice(name string) []int { + v, _ := o.GetIntSlice(name) + return v +} + +func (o *Extensions) GetIntSlice(name string) ([]int, error) { + v, err := o.Get(name) + if err != nil { + return []int{}, err + } + + return cast.ToIntSliceE(v) +} + +func (o *Extensions) MustGetStringSlice(name string) []string { + v, _ := o.GetStringSlice(name) + return v +} + +func (o *Extensions) GetStringSlice(name string) ([]string, error) { + v, err := o.Get(name) + if err != nil { + return []string{}, err + } + + return cast.ToStringSliceE(v) +} + +func (o *Extensions) MustGetStringMap(name string) map[string]any { + v, _ := o.GetStringMap(name) + return v +} + +func (o *Extensions) GetStringMap(name string) (map[string]any, error) { + v, err := o.Get(name) + if err != nil { + return map[string]any{}, err + } + + return cast.ToStringMapE(v) +} + +func (o *Extensions) MustGetStringMapString(name string) map[string]string { + v, _ := o.GetStringMapString(name) + return v +} + +func (o *Extensions) GetStringMapString(name string) (map[string]string, error) { + v, err := o.Get(name) + if err != nil { + return map[string]string{}, err + } + + return cast.ToStringMapStringE(v) +} + +func (o *Extensions) Set(name string, value any) error { + if o.IExtensionsValue == nil { + return fmt.Errorf("%w: %s", ErrExtensionNotFound, name) + } + + extType := reflect.TypeOf(o.IExtensionsValue) + extVal := reflect.ValueOf(o.IExtensionsValue) + if extType.Kind() == reflect.Pointer { + extType = extType.Elem() + extVal = extVal.Elem() + } + + var fieldName, fieldJSONTag, fieldCBORTag string + for i := 0; i < extVal.NumField(); i++ { + typeField := extType.Field(i) + valField := extVal.Field(i) + fieldName = typeField.Name + + tag, ok := typeField.Tag.Lookup("json") + if ok { + fieldJSONTag = strings.Split(tag, ",")[0] + } + + tag, ok = typeField.Tag.Lookup("cbor") + if ok { + fieldCBORTag = strings.Split(tag, ",")[0] + } + + if fieldName == name || fieldJSONTag == name || fieldCBORTag == name { + newVal := reflect.ValueOf(value) + if newVal.CanConvert(valField.Type()) { + valField.Set(newVal.Convert(valField.Type())) + return nil + } + + return fmt.Errorf( + "cannot set field %q (of type %s) to %v (%T)", + name, typeField.Type.Name(), + value, value, + ) + } + } + + return fmt.Errorf("%w: %s", ErrExtensionNotFound, name) +} diff --git a/extensions/extensions_test.go b/extensions/extensions_test.go new file mode 100644 index 00000000..74d44f52 --- /dev/null +++ b/extensions/extensions_test.go @@ -0,0 +1,122 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package extensions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type Entity struct { + EntityName string + Roles []int64 + + Extensions +} + +type TestExtensions struct { + Address string `cbor:"-1,keyasint,omitempty" json:"address,omitempty"` + Size int `cbor:"-2,keyasint,omitempty" json:"size,omitempty"` + YearsOnAir float32 `cbor:"-3,keyasint,omitempty" json:"years-on-air,omitempty"` + StillAiring bool `cbor:"-4,keyasint,omitempty" json:"still-airing,omitempty"` + Ages []int `cbor:"-5,keyasint,omitempty" json:"ages,omitempty"` + Jobs map[string]string `cbor:"-6,keyasint,omitempty" json:"jobs,omitempty"` +} + +func TestExtensions_Register(t *testing.T) { + exts := Extensions{} + assert.False(t, exts.HaveExtensions()) + + exts.Register(&TestExtensions{}) + assert.True(t, exts.HaveExtensions()) + + badRegister := func() { + exts.Register(TestExtensions{}) + } + + assert.Panics(t, badRegister) +} + +func TestExtensions_GetSet(t *testing.T) { + extsVal := TestExtensions{ + Address: "742 Evergreen Terrace", + Size: 6, + YearsOnAir: 33.8, + StillAiring: true, + Ages: []int{2, 7, 8, 10, 37, 38}, + Jobs: map[string]string{ + "Homer": "safety inspector", + "Marge": "housewife", + "Bart": "elementary school student", + "Lisa": "elementary school student", + }, + } + exts := Extensions{IExtensionsValue: &extsVal} + + v, err := exts.GetInt("size") + assert.NoError(t, err) + assert.Equal(t, 6, v) + + assert.Equal(t, 6, exts.MustGetInt("size")) + assert.Equal(t, int64(6), exts.MustGetInt64("size")) + assert.Equal(t, int32(6), exts.MustGetInt32("size")) + assert.Equal(t, int16(6), exts.MustGetInt16("size")) + assert.Equal(t, int8(6), exts.MustGetInt8("size")) + + assert.Equal(t, uint(6), exts.MustGetUint("size")) + assert.Equal(t, uint64(6), exts.MustGetUint64("size")) + assert.Equal(t, uint32(6), exts.MustGetUint32("size")) + assert.Equal(t, uint16(6), exts.MustGetUint16("size")) + assert.Equal(t, uint8(6), exts.MustGetUint8("size")) + + assert.InEpsilon(t, float32(33.8), exts.MustGetFloat32("years-on-air"), 0.000001) + assert.InEpsilon(t, float64(33.8), exts.MustGetFloat64("-3"), 0.000001) + + assert.Equal(t, true, exts.MustGetBool("StillAiring")) + + _, err = exts.GetSlice("ages") + assert.EqualError(t, err, + `unable to cast []int{2, 7, 8, 10, 37, 38} of type []int to []interface{}`) + assert.Nil(t, exts.MustGetSlice("ages")) + + assert.EqualValues(t, []int{2, 7, 8, 10, 37, 38}, exts.MustGetIntSlice("ages")) + assert.EqualValues(t, []string{"2", "7", "8", "10", "37", "38"}, + exts.MustGetStringSlice("ages")) + + assert.EqualValues(t, map[string]string{ + "Homer": "safety inspector", + "Marge": "housewife", + "Bart": "elementary school student", + "Lisa": "elementary school student", + }, exts.MustGetStringMapString("jobs")) + + _, err = exts.GetStringMap("jobs") + assert.EqualError(t, err, + `unable to cast map[string]string{"Bart":"elementary school student", "Homer":"safety inspector", "Lisa":"elementary school student", "Marge":"housewife"} of type map[string]string to map[string]interface{}`) + m := exts.MustGetStringMap("jobs") + assert.Equal(t, map[string]any{}, m) + + s, err := exts.GetString("address") + assert.NoError(t, err) + assert.Equal(t, "742 Evergreen Terrace", s) + + _, err = exts.GetInt("address") + assert.EqualError(t, err, `unable to cast "742 Evergreen Terrace" of type string to int`) + + _, err = exts.GetInt("foo") + assert.EqualError(t, err, "extension not found: foo") + + err = exts.Set("-1", "123 Fake Street") + assert.NoError(t, err) + + s, err = exts.GetString("address") + assert.NoError(t, err) + assert.Equal(t, "123 Fake Street", s) + + err = exts.Set("Size", "foo") + assert.EqualError(t, err, `cannot set field "Size" (of type int) to foo (string)`) + + assert.Equal(t, "", exts.MustGetString("does-not-exist")) + assert.Equal(t, 0, exts.MustGetInt("does-not-exist")) +} diff --git a/go.mod b/go.mod index 9d95f047..96a5aa49 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,12 @@ module github.com/veraison/corim go 1.18 require ( - github.com/fxamacker/cbor/v2 v2.4.0 + github.com/fxamacker/cbor/v2 v2.5.0 github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.0 github.com/lestrrat-go/jwx/v2 v2.0.8 github.com/spf13/afero v1.9.2 + github.com/spf13/cast v1.4.1 github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.8.2 @@ -35,7 +36,6 @@ require ( github.com/moogar0880/problems v0.1.1 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.2.0 // indirect diff --git a/go.sum b/go.sum index c6347523..e1ae4e55 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,8 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= From 64e8c5f1fb17ca7fc3f2a160a317e3745fd228c4 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 13 Sep 2023 17:48:30 +0100 Subject: [PATCH 015/110] Implement type choice extensions Implement an extensions mechanism for entries identified as "type-choice" in the [CoRIM] spec draft 2. This done by defining a common registration pattern for ITypeChoiceValue interface implementations (the interface is often extended for specific extension points). A Register function is used to associate a factory that produces a type choice instance populated with an appropriate value with a CBOR tag. The factory function must be able to handle nil values, resulting in the zero-value for the associated type; aside from the, the range of valid values the factory can handle is type-specific. A couple of the entries indicates as type choices in the spec (rel and role) are in fact extensible enums, rather than types, so they're implemented somewhat differently. The registration function for them just takes the new value and associated name; there is no need for a factory function. Additionally: - add tagged-int-type implementation for class-id - add EntityName type to represent entity-name type choice (entity-name was previously implemented as a string) - add String() method for comid.Role and corim.Role [CoRIM]: https://www.ietf.org/archive/id/draft-ietf-rats-corim-02.html Signed-off-by: Sergei Trofimov --- comid/attestverifkey_test.go | 5 +- comid/cbor.go | 31 +- comid/ccaplatformconfigid.go | 75 ++++- comid/ccaplatformconfigid_test.go | 64 ++++ comid/class.go | 32 +- comid/classid.go | 482 ++++++++++++++++---------- comid/classid_test.go | 272 +++++++++++++-- comid/comid.go | 2 +- comid/cryptokey.go | 327 +++++++++++------- comid/cryptokey_test.go | 67 +++- comid/devidentitykey_test.go | 4 +- comid/entity.go | 239 ++++++++++++- comid/entity_test.go | 184 ++++++++++ comid/environment_test.go | 32 +- comid/example_cca_refval_test.go | 18 +- comid/example_psa_keys_test.go | 7 +- comid/example_psa_refval_test.go | 14 +- comid/example_test.go | 42 ++- comid/group.go | 157 +++++---- comid/group_test.go | 62 ++++ comid/instance.go | 286 +++++++++------- comid/instance_test.go | 67 +++- comid/measurement.go | 540 +++++++++++++++++------------- comid/measurement_test.go | 321 +++++++++--------- comid/oid.go | 77 +++++ comid/psareferencevalue.go | 100 +++++- comid/psareferencevalue_test.go | 18 +- comid/referencevalue_test.go | 3 +- comid/rel.go | 43 ++- comid/rel_test.go | 17 + comid/role.go | 29 ++ comid/role_test.go | 22 ++ comid/svn.go | 276 +++++++++++---- comid/svn_test.go | 182 ++++++++++ comid/ueid.go | 71 +++- comid/ueid_test.go | 30 ++ comid/uuid.go | 91 ++++- comid/uuid_test.go | 24 ++ corim/cbor.go | 29 +- corim/entity.go | 246 +++++++++++++- corim/entity_test.go | 192 ++++++++++- corim/extensions_test.go | 4 +- corim/role.go | 32 +- corim/role_test.go | 23 ++ corim/unsignedcorim_test.go | 5 +- encoding/json.go | 32 ++ encoding/json_test.go | 31 ++ extensions/typechoice.go | 36 ++ 48 files changed, 3832 insertions(+), 1111 deletions(-) create mode 100644 comid/group_test.go create mode 100644 comid/svn_test.go create mode 100644 comid/ueid_test.go create mode 100644 comid/uuid_test.go create mode 100644 extensions/typechoice.go diff --git a/comid/attestverifkey_test.go b/comid/attestverifkey_test.go index 659a0abc..20c791d9 100644 --- a/comid/attestverifkey_test.go +++ b/comid/attestverifkey_test.go @@ -23,16 +23,17 @@ func TestAttestVerifKey_Valid_empty(t *testing.T) { testerr: "environment validation failed: environment must not be empty", }, { - env: Environment{Instance: NewInstanceUEID(TestUEID)}, + env: Environment{Instance: MustNewUEIDInstance(TestUEID)}, verifkey: CryptoKeys{}, testerr: "verification keys validation failed: no keys to validate", }, { - env: Environment{Instance: NewInstanceUEID(TestUEID)}, + env: Environment{Instance: MustNewUEIDInstance(TestUEID)}, verifkey: CryptoKeys{&invalidKey}, testerr: "verification keys validation failed: invalid key at index 0: key value not set", }, } + for _, tv := range tvs { av := AttestVerifKey{Environment: tv.env, VerifKeys: tv.verifkey} err := av.Valid() diff --git a/comid/cbor.go b/comid/cbor.go index c44d518d..ab8a9233 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -4,6 +4,7 @@ package comid import ( + "fmt" "reflect" cbor "github.com/fxamacker/cbor/v2" @@ -12,16 +13,14 @@ import ( var ( em, emError = initCBOREncMode() dm, dmError = initCBORDecMode() -) -func comidTags() cbor.TagSet { - comidTagsMap := map[uint64]interface{}{ + comidTagsMap = map[uint64]interface{}{ 32: TaggedURI(""), 37: TaggedUUID{}, 111: TaggedOID{}, // CoMID tags 550: TaggedUEID{}, - //551: To Do see: https://github.com/veraison/corim/issues/32 + 551: TaggedInt(0), 552: TaggedSVN(0), 553: TaggedMinSVN(0), 554: TaggedPKIXBase64Key(""), @@ -37,7 +36,9 @@ func comidTags() cbor.TagSet { 601: TaggedPSARefValID{}, 602: TaggedCCAPlatformConfigID(""), } +) +func comidTags() cbor.TagSet { opts := cbor.TagOptions{ EncTag: cbor.EncTagRequired, DecTag: cbor.DecTagRequired, @@ -69,6 +70,28 @@ func initCBORDecMode() (dm cbor.DecMode, err error) { return decOpt.DecModeWithTags(comidTags()) } +func registerCOMIDTag(tag uint64, t interface{}) error { + if _, exists := comidTagsMap[tag]; exists { + return fmt.Errorf("tag %d is already registered", tag) + } + + comidTagsMap[tag] = t + + var err error + + em, err = initCBOREncMode() + if err != nil { + return err + } + + dm, err = initCBORDecMode() + if err != nil { + return err + } + + return nil +} + func init() { if emError != nil { panic(emError) diff --git a/comid/ccaplatformconfigid.go b/comid/ccaplatformconfigid.go index e485083f..a55271ff 100644 --- a/comid/ccaplatformconfigid.go +++ b/comid/ccaplatformconfigid.go @@ -3,11 +3,16 @@ package comid -import "fmt" +import ( + "encoding/json" + "errors" + "fmt" + "unicode/utf8" +) -type CCAPlatformConfigID string +var CCAPlatformConfigIDType = "cca.platform-config-id" -type TaggedCCAPlatformConfigID CCAPlatformConfigID +type CCAPlatformConfigID string func (o CCAPlatformConfigID) Empty() bool { return o == "" @@ -27,3 +32,67 @@ func (o CCAPlatformConfigID) Get() (CCAPlatformConfigID, error) { } return o, nil } + +type TaggedCCAPlatformConfigID CCAPlatformConfigID + +func NewTaggedCCAPlatformConfigID(val any) (*TaggedCCAPlatformConfigID, error) { + var ret TaggedCCAPlatformConfigID + + if val == nil { + return &ret, nil + } + + switch t := val.(type) { + case TaggedCCAPlatformConfigID: + ret = t + case *TaggedCCAPlatformConfigID: + ret = *t + case CCAPlatformConfigID: + ret = TaggedCCAPlatformConfigID(t) + case *CCAPlatformConfigID: + ret = TaggedCCAPlatformConfigID(*t) + case string: + ret = TaggedCCAPlatformConfigID(t) + case []byte: + if !utf8.Valid(t) { + return nil, errors.New("bytes do not form a valid UTF-8 string") + } + ret = TaggedCCAPlatformConfigID(t) + default: + return nil, fmt.Errorf("unexpected type for CCA platform-config-id: %T", t) + } + + return &ret, nil +} + +func (o TaggedCCAPlatformConfigID) Valid() error { + if o == "" { + return errors.New("empty value") + } + + return nil +} + +func (o TaggedCCAPlatformConfigID) String() string { + return string(o) +} + +func (o TaggedCCAPlatformConfigID) Type() string { + return CCAPlatformConfigIDType +} + +func (o TaggedCCAPlatformConfigID) IsZero() bool { + return len(o) == 0 +} + +func (o *TaggedCCAPlatformConfigID) UnmarshalJSON(data []byte) error { + var temp string + + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + + *o = TaggedCCAPlatformConfigID(temp) + + return nil +} diff --git a/comid/ccaplatformconfigid_test.go b/comid/ccaplatformconfigid_test.go index b0a0a37c..543390fd 100644 --- a/comid/ccaplatformconfigid_test.go +++ b/comid/ccaplatformconfigid_test.go @@ -29,3 +29,67 @@ func TestCCAPlatformConfigID_Get_nok(t *testing.T) { _, err := cca.Get() assert.EqualError(t, err, expectedErr) } + +func TestNewTaggedCCAPlatformConfigID(t *testing.T) { + testID := TaggedCCAPlatformConfigID("test") + untagged := CCAPlatformConfigID("test") + + for _, tv := range []struct { + Name string + Input any + Err string + Expected TaggedCCAPlatformConfigID + }{ + { + Name: "TaggedCCAPlatformConfigID ok", + Input: testID, + Expected: testID, + }, + { + Name: "*TaggedCCAPlatformConfigID ok", + Input: &testID, + Expected: testID, + }, + { + Name: "CCAPlatformConfigID ok", + Input: untagged, + Expected: testID, + }, + { + Name: "*CCAPlatformConfigID ok", + Input: &untagged, + Expected: testID, + }, + { + Name: "string ok", + Input: "test", + Expected: testID, + }, + { + Name: "[]byte ok", + Input: []byte{0x74, 0x65, 0x73, 0x74}, + Expected: testID, + }, + { + Name: "[]byte not ok", + Input: []byte{0x80, 0x65, 0x73, 0x74}, + Err: "bytes do not form a valid UTF-8 string", + }, + { + Name: "bad type", + Input: 7, + Err: "unexpected type for CCA platform-config-id: int", + }, + } { + t.Run(tv.Name, func(t *testing.T) { + out, err := NewTaggedCCAPlatformConfigID(tv.Input) + + if tv.Err != "" { + assert.Nil(t, out) + assert.EqualError(t, err, tv.Err) + } else { + assert.Equal(t, tv.Expected, *out) + } + }) + } +} diff --git a/comid/class.go b/comid/class.go index 085e703b..ab414740 100644 --- a/comid/class.go +++ b/comid/class.go @@ -23,39 +23,33 @@ type Class struct { // NewClassUUID instantiates a new Class object with the specified UUID as // identifier func NewClassUUID(uuid UUID) *Class { - c := Class{ - ClassID: &ClassID{}, - } - - if c.ClassID.SetUUID(uuid) == nil { + classID, err := NewUUIDClassID(uuid) + if err != nil { return nil } - return &c + + return &Class{ClassID: classID} } // NewClassImplID instantiates a new Class object that identifies the specified PSA // Implementation ID func NewClassImplID(implID ImplID) *Class { - c := Class{ - ClassID: &ClassID{}, - } - - if c.ClassID.SetImplID(implID) == nil { + classID, err := NewImplIDClassID(implID) + if err != nil { return nil } - return &c + + return &Class{ClassID: classID} } // NewClassOID instantiates a new Class object that identifies the OID func NewClassOID(oid string) *Class { - c := Class{ - ClassID: &ClassID{}, - } - - if c.ClassID.SetOID(oid) == nil { + classID, err := NewOIDClassID(oid) + if err != nil { return nil } - return &c + + return &Class{ClassID: classID} } // SetVendor sets the vendor metadata to the supplied string @@ -131,7 +125,7 @@ func (o *Class) SetIndex(index uint64) *Class { // Valid checks the non-empty<> constraint on the map func (o Class) Valid() error { // check non-empty<{ ... }> - if (o.ClassID == nil || o.ClassID.Unset()) && + if (o.ClassID == nil || !o.ClassID.IsSet()) && o.Vendor == nil && o.Model == nil && o.Layer == nil && o.Index == nil { return fmt.Errorf("class must not be empty") } diff --git a/comid/classid.go b/comid/classid.go index 760f8e5d..acad47d5 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -5,245 +5,383 @@ package comid import ( "encoding/base64" + "encoding/binary" "encoding/json" + "errors" "fmt" + "strconv" + + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" ) -// ClassID represents a $class-id-type-choice, which can be one of TaggedUUID, -// TaggedOID, or TaggedImplID (PSA-specific extension) +// ClassID identifies the environment via a well-known identifier. This can be +// an OID, a UUID, or a profile-defined extension type. type ClassID struct { - val interface{} + Value IClassIDValue } -type ClassIDType uint16 +// NewClassID creates a new ClassID of the specified type using the specified value. +func NewClassID(val any, typ string) (*ClassID, error) { + factory, ok := classIDValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unknown class id type: %s", typ) + } -const ( - ClassIDTypeUUID = ClassIDType(iota) - ClassIDTypeImplID - ClassIDTypeOID + return factory(val) +} - ClassIDTypeUnknown = ^ClassIDType(0) -) +// Valid returns nil if the ClassID is valid, or an error describing the +// problem, if it is not. +func (o ClassID) Valid() error { + if o.Value == nil { + return errors.New("nil value") + } -// SetUUID sets the value of the targed ClassID to the supplied UUID -func (o *ClassID) SetUUID(uuid UUID) *ClassID { - if o != nil { - o.val = TaggedUUID(uuid) + return o.Value.Valid() +} + +// Type returns the type of the ClassID +func (o ClassID) Type() string { + if o.Value == nil { + return "" } - return o + + return o.Value.Type() } -type ImplID [32]byte -type TaggedImplID ImplID +// Bytes returns a []byte containing the raw bytes of the class id value +func (o ClassID) Bytes() []byte { + if o.Value == nil { + return []byte{} + } + return o.Value.Bytes() +} -func (o ImplID) MarshalJSON() ([]byte, error) { - return json.Marshal(o[:]) +// IsSet returns true iff the underlying class id value has been set (is not nil) +func (o ClassID) IsSet() bool { + return o.Value != nil } -func (o *ImplID) UnmarshalJSON(data []byte) error { - var b []byte +// MarshalCBOR serializes the target ClassID to CBOR +func (o ClassID) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.Value) +} - if err := json.Unmarshal(data, &b); err != nil { - return fmt.Errorf("bad ImplID: %w", err) +// UnmarshalCBOR deserializes the supplied CBOR buffer into the target ClassID. +// It is undefined behavior to try and inspect the target ClassID in case this +// method returns an error. +func (o *ClassID) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.Value) +} + +// UnmarshalJSON deserializes the supplied JSON object into the target ClassID +// The class id object must have the following shape: +// +// { +// "type": "", +// "value": +// } +// +// where must be one of the known IClassIDValue implementation +// type names (available in this implementation: "uuid", "oid", +// "psa.impl-id", "int"), and is the JSON encoding of the underlying +// class id value. The exact encoding is dependent. For the base +// implementation types it is +// +// oid: dot-separated integers, e.g. "1.2.3.4" +// psa.impl-id: base64-encoded bytes, e.g. "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" +// uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" +// int: an integer value, e.g. 7 +func (o *ClassID) UnmarshalJSON(data []byte) error { + var tnv encoding.TypeAndValue + + if err := json.Unmarshal(data, &tnv); err != nil { + return fmt.Errorf("class id decoding failure: %w", err) } - if nb := len(b); nb != 32 { - return fmt.Errorf("bad ImplID format: got %d bytes, want 32", nb) + decoded, err := NewClassID(nil, tnv.Type) + if err != nil { + return err + } + + if err := json.Unmarshal(tnv.Value, &decoded.Value); err != nil { + return fmt.Errorf( + "cannot unmarshal class id: %w", + err, + ) + } + + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) } - copy(o[:], b) + o.Value = decoded.Value return nil } -type TaggedOID OID +// MarshalJSON serializes the target ClassID to JSON +func (o ClassID) MarshalJSON() ([]byte, error) { + return extensions.TypeChoiceValueMarshalJSON(o.Value) +} -// SetImplID sets the value of the targed ClassID to the supplied PSA -// Implementation ID (see Section 3.2.2 of draft-tschofenig-rats-psa-token) -func (o *ClassID) SetImplID(implID ImplID) *ClassID { - if o != nil { - o.val = TaggedImplID(implID) +// String returns a printable string of the ClassID value. UUIDs use the +// canonical 8-4-4-4-12 format, PSA Implementation IDs are base64 encoded. +// OIDs are output in dotted-decimal notation. +func (o ClassID) String() string { + if o.Value == nil { + return "" } - return o + + return o.Value.String() } -func (o ClassID) GetImplID() (ImplID, error) { - switch t := o.val.(type) { +type IClassIDValue interface { + extensions.ITypeChoiceValue + + Bytes() []byte +} + +const ImplIDType = "psa.impl-id" + +type ImplID [32]byte + +func (o ImplID) String() string { + return base64.StdEncoding.EncodeToString(o[:]) +} + +func (o ImplID) Valid() error { + return nil +} + +type TaggedImplID ImplID + +func NewImplIDClassID(val any) (*ClassID, error) { + var ret TaggedImplID + + if val == nil { + return &ClassID{&TaggedImplID{}}, nil + } + + switch t := val.(type) { + case []byte: + if nb := len(t); nb != 32 { + return nil, fmt.Errorf("bad psa.impl-id: got %d bytes, want 32", nb) + } + + copy(ret[:], t) + case string: + v, err := base64.StdEncoding.DecodeString(t) + if err != nil { + return nil, fmt.Errorf("bad psa.impl-id: %w", err) + } + + if nb := len(v); nb != 32 { + return nil, fmt.Errorf("bad psa.impl-id: decoded %d bytes, want 32", nb) + } + + copy(ret[:], v) case TaggedImplID: - return ImplID(t), nil + copy(ret[:], t[:]) + case *TaggedImplID: + copy(ret[:], (*t)[:]) + case ImplID: + copy(ret[:], t[:]) + case *ImplID: + copy(ret[:], (*t)[:]) default: - return ImplID{}, fmt.Errorf("class-id type is: %T", t) + return nil, fmt.Errorf("unexpected type for psa.impl-id: %T", t) } + + return &ClassID{&ret}, nil } -// SetOID sets the value of the targed ClassID to the supplied OID. -// The OID is a string in dotted-decimal notation -func (o *ClassID) SetOID(s string) *ClassID { - if o != nil { - var berOID OID - if berOID.FromString(s) != nil { - return nil - } - o.val = TaggedOID(berOID) +func MustNewImplIDClassID(val any) *ClassID { + ret, err := NewImplIDClassID(val) + if err != nil { + panic(err) } - return o + + return ret } -// MarshalCBOR serializes the target ClassID to CBOR -func (o ClassID) MarshalCBOR() ([]byte, error) { - return em.Marshal(o.val) +func (o TaggedImplID) Valid() error { + return ImplID(o).Valid() } -// UnmarshalCBOR deserializes the supplied CBOR buffer into the target ClassID. -// It is undefined behavior to try and inspect the target ClassID in case this -// method returns an error. -func (o *ClassID) UnmarshalCBOR(data []byte) error { - var implID TaggedImplID +func (o TaggedImplID) String() string { + return ImplID(o).String() +} - if dm.Unmarshal(data, &implID) == nil { - o.val = implID - return nil +func (o TaggedImplID) Type() string { + return ImplIDType +} + +func (o TaggedImplID) Bytes() []byte { + return o[:] +} + +func (o *TaggedImplID) MarshalJSON() ([]byte, error) { + return json.Marshal((*o)[:]) +} + +func (o *TaggedImplID) UnmarshalJSON(data []byte) error { + var out []byte + if err := json.Unmarshal(data, &out); err != nil { + return err } - var uuid TaggedUUID + if len(out) != 32 { + return fmt.Errorf("bad psa.impl-id: decoded %d bytes, want 32", len(out)) + } - if dm.Unmarshal(data, &uuid) == nil { - o.val = uuid - return nil + copy((*o)[:], out) + + return nil +} + +func NewOIDClassID(val any) (*ClassID, error) { + ret, err := NewTaggedOID(val) + if err != nil { + return nil, err } - var oid TaggedOID + return &ClassID{ret}, nil +} - if dm.Unmarshal(data, &oid) == nil { - o.val = oid - return nil +func MustNewOIDClassID(val any) *ClassID { + ret, err := NewOIDClassID(val) + if err != nil { + panic(err) } - return fmt.Errorf("unknown class id (CBOR: %x)", data) + return ret } -// UnmarshalJSON deserializes the supplied JSON object into the target ClassID -// The class id object must have one of the following shapes: -// -// UUID: -// -// { -// "type": "uuid", -// "value": "69E027B2-7157-4758-BCB4-D9F167FE49EA" -// } -// -// OID: -// -// { -// "type": "oid", -// "value": "2.16.840.1.113741.1.15.4.2" -// } -// -// PSA Implementation ID: -// -// { -// "type": "psa.impl-id", -// "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" -// } -func (o *ClassID) UnmarshalJSON(data []byte) error { - var v tnv +func NewUUIDClassID(val any) (*ClassID, error) { + if val == nil { + return &ClassID{&TaggedUUID{}}, nil + } - if err := json.Unmarshal(data, &v); err != nil { - return err + ret, err := NewTaggedUUID(val) + if err != nil { + return nil, err } - switch v.Type { - case "uuid": // nolint: goconst - var x UUID - if err := x.UnmarshalJSON(v.Value); err != nil { - return err - } - o.val = TaggedUUID(x) - case "oid": - var x OID - if err := x.UnmarshalJSON(v.Value); err != nil { - return err - } - o.val = TaggedOID(x) - case "psa.impl-id": - var x ImplID - if err := x.UnmarshalJSON(v.Value); err != nil { - return err - } - o.val = TaggedImplID(x) - default: - return fmt.Errorf("unknown type '%s' for class id", v.Type) + return &ClassID{ret}, nil +} + +func MustNewUUIDClassID(val any) *ClassID { + ret, err := NewUUIDClassID(val) + if err != nil { + panic(err) } - return nil + return ret } -// MarshalJSON serializes the target ClassID to JSON -func (o ClassID) MarshalJSON() ([]byte, error) { - var ( - v tnv - b []byte - err error - ) - - switch t := o.val.(type) { - case TaggedUUID: - b, err = UUID(t).MarshalJSON() - if err != nil { - return nil, err - } - v = tnv{Type: "uuid", Value: b} - case TaggedOID: - b, err = OID(t).MarshalJSON() +const IntType = "int" + +type TaggedInt int + +func NewIntClassID(val any) (*ClassID, error) { + if val == nil { + zeroVal := TaggedInt(0) + return &ClassID{&zeroVal}, nil + } + + var ret TaggedInt + + switch t := val.(type) { + case string: + i, err := strconv.Atoi(t) if err != nil { - return nil, err + return nil, fmt.Errorf("bad int: %w", err) } - v = tnv{Type: "oid", Value: b} - case TaggedImplID: - b, err = ImplID(t).MarshalJSON() - if err != nil { - return nil, err + ret = TaggedInt(i) + case []byte: + if len(t) != 8 { + return nil, fmt.Errorf("bad int: want 8 bytes, got %d bytes", len(t)) } - v = tnv{Type: "psa.impl-id", Value: b} + ret = TaggedInt(binary.BigEndian.Uint64(t)) + case int: + ret = TaggedInt(t) + case *int: + ret = TaggedInt(*t) + case int64: + ret = TaggedInt(t) + case *int64: + ret = TaggedInt(*t) + case uint64: + ret = TaggedInt(t) + case *uint64: + ret = TaggedInt(*t) default: - return nil, fmt.Errorf("unknown type %T for class-id", t) + return nil, fmt.Errorf("unexpected type for int: %T", t) } - return json.Marshal(v) + if err := ret.Valid(); err != nil { + return nil, err + } + + return &ClassID{&ret}, nil } -// Type returns the type of the target ClassID, i.e., one of UUID, OID or PSA -// Implementation ID -func (o ClassID) Type() ClassIDType { - switch o.val.(type) { - case TaggedUUID: - return ClassIDTypeUUID - case TaggedImplID: - return ClassIDTypeImplID - case TaggedOID: - return ClassIDTypeOID - } - return ClassIDTypeUnknown +func (o TaggedInt) String() string { + return fmt.Sprint(int(o)) } -// String returns a printable string of the ClassID value. UUIDs use the -// canonical 8-4-4-4-12 format, PSA Implementation IDs are base64 encoded. -// OIDs are output in dotted-decimal notation. -func (o ClassID) String() string { - switch t := o.val.(type) { - case TaggedUUID: - return UUID(t).String() - case TaggedImplID: - b := [32]byte(t) - return base64.StdEncoding.EncodeToString(b[:]) - case TaggedOID: - return OID(t).String() - default: - return "" - } +func (o TaggedInt) Valid() error { + return nil +} + +func (o TaggedInt) Type() string { + return "int" } -// Unset tests whether the target ClassID has been initialized -func (o ClassID) Unset() bool { - return o.val == nil || o.Type() == ClassIDTypeUnknown +func (o TaggedInt) Bytes() []byte { + var ret [8]byte + binary.BigEndian.PutUint64(ret[:], uint64(o)) + return ret[:] +} + +// IClassIDFactory defines the signature for the factory functions that may be +// registred using RegisterClassIDType to provide a new implementation of the +// corresponding type choice. The factory function should create a new *ClassID +// with the underlying value created based on the provided input. The range of +// valid inputs is up to the specific type choice implementation, however it +// _must_ accept nil as one of the inputs, and return the Zero value for +// implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IClassIDFactory func(any) (*ClassID, error) + +var classIDValueRegister = map[string]IClassIDFactory{ + OIDType: NewOIDClassID, + UUIDType: NewUUIDClassID, + IntType: NewIntClassID, + + ImplIDType: NewImplIDClassID, +} + +// RegisterClassIDType registers a new IClassIDValue implementation (created +// by the provided IClassIDFactory) under the specified CBOR tag. +func RegisterClassIDType(tag uint64, factory IClassIDFactory) error { + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Type() + if _, exists := classIDValueRegister[typ]; exists { + return fmt.Errorf("class ID type with name %q already exists", typ) + } + + if err := registerCOMIDTag(tag, nilVal.Value); err != nil { + return err + } + + classIDValueRegister[typ] = factory + + return nil } diff --git a/comid/classid_test.go b/comid/classid_test.go index 51b07391..aaeeb393 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -4,6 +4,8 @@ package comid import ( + "encoding/binary" + "encoding/json" "fmt" "testing" @@ -12,9 +14,7 @@ import ( ) func TestClassID_MarshalCBOR_UUID(t *testing.T) { - var tv ClassID - - require.NotNil(t, tv.SetUUID(TestUUID)) + tv := MustNewUUIDClassID(TestUUID) // 37(h'31FB5ABF023E4992AA4E95F9C1503BFA') // tag(37): d8 25 @@ -29,9 +29,7 @@ func TestClassID_MarshalCBOR_UUID(t *testing.T) { } func TestClassID_MarshalCBOR_ImplID(t *testing.T) { - var tv ClassID - - require.NotNil(t, tv.SetImplID(TestImplID)) + tv := MustNewImplIDClassID(TestImplID) // 600 (h'61636D652D696D706C656D656E746174696F6E2D69642D303030303030303031') // tag(600): d9 0258 @@ -66,7 +64,7 @@ func TestClassID_UnmarshalCBOR_UUID_OK(t *testing.T) { err := actual.UnmarshalCBOR(tv) assert.Nil(t, err) - assert.Equal(t, ClassIDTypeUUID, actual.Type()) + assert.Equal(t, "uuid", actual.Type()) assert.Equal(t, TestUUIDString, actual.String()) } @@ -79,7 +77,7 @@ func TestClassID_UnmarshalCBOR_ImplID_OK(t *testing.T) { err := actual.UnmarshalCBOR(tv) assert.Nil(t, err) - assert.Equal(t, ClassIDTypeImplID, actual.Type()) + assert.Equal(t, "psa.impl-id", actual.Type()) assert.Equal(t, expected, actual.String()) } @@ -88,12 +86,10 @@ func TestClassID_UnmarshalCBOR_badInput(t *testing.T) { hex := "582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031" tv := MustHexDecode(t, hex) - expectedError := fmt.Sprintf("unknown class id (CBOR: %s)", hex) - var actual ClassID err := actual.UnmarshalCBOR(tv) - assert.EqualError(t, err, expectedError) + assert.EqualError(t, err, "cbor: cannot unmarshal byte string into Go value of type comid.IClassIDValue") } func TestClassID_UnmarshalJSON_UUID(t *testing.T) { @@ -105,7 +101,7 @@ func TestClassID_UnmarshalJSON_UUID(t *testing.T) { err := actual.UnmarshalJSON([]byte(tv)) assert.Nil(t, err) - assert.Equal(t, ClassIDTypeUUID, actual.Type()) + assert.Equal(t, "uuid", actual.Type()) assert.Equal(t, TestUUIDString, actual.String()) } @@ -122,7 +118,7 @@ func TestClassID_UnmarshalJSON_ImplID(t *testing.T) { err := actual.UnmarshalJSON([]byte(tv)) assert.Nil(t, err) - assert.Equal(t, ClassIDTypeImplID, actual.Type()) + assert.Equal(t, "psa.impl-id", actual.Type()) // the returned string is the base64 encoding of the stored binary assert.Equal(t, expected, actual.String()) } @@ -133,8 +129,8 @@ func TestClassID_UnmarshalJSON_badInput_unknown_type(t *testing.T) { var actual ClassID err := actual.UnmarshalJSON([]byte(tv)) - assert.EqualError(t, err, "unknown type 'FOOBAR' for class id") - assert.Equal(t, ClassIDTypeUnknown, actual.Type()) + assert.EqualError(t, err, "unknown class id type: FOOBAR") + assert.Equal(t, "", actual.Type()) } func TestClassID_UnmarshalJSON_badInput_missing_value(t *testing.T) { @@ -143,8 +139,8 @@ func TestClassID_UnmarshalJSON_badInput_missing_value(t *testing.T) { var actual ClassID err := actual.UnmarshalJSON([]byte(tv)) - assert.EqualError(t, err, "bad ImplID: unexpected end of JSON input") - assert.Equal(t, ClassIDTypeUnknown, actual.Type()) + assert.EqualError(t, err, "class id decoding failure: no value provided for psa.impl-id") + assert.Equal(t, "", actual.Type()) } func TestClassID_UnmarshalJSON_badInput_empty_value(t *testing.T) { @@ -153,8 +149,8 @@ func TestClassID_UnmarshalJSON_badInput_empty_value(t *testing.T) { var actual ClassID err := actual.UnmarshalJSON([]byte(tv)) - assert.EqualError(t, err, "bad ImplID format: got 0 bytes, want 32") - assert.Equal(t, ClassIDTypeUnknown, actual.Type()) + assert.EqualError(t, err, "cannot unmarshal class id: bad psa.impl-id: decoded 0 bytes, want 32") + assert.Equal(t, "", actual.Type()) } func TestClassID_UnmarshalJSON_badInput_badly_encoded_ImplID_value(t *testing.T) { @@ -163,8 +159,8 @@ func TestClassID_UnmarshalJSON_badInput_badly_encoded_ImplID_value(t *testing.T) var actual ClassID err := actual.UnmarshalJSON([]byte(tv)) - assert.EqualError(t, err, "bad ImplID: illegal base64 data at input byte 0") - assert.Equal(t, ClassIDTypeUnknown, actual.Type()) + assert.EqualError(t, err, "cannot unmarshal class id: illegal base64 data at input byte 0") + assert.Equal(t, "", actual.Type()) } func TestClassID_UnmarshalJSON_badInput_badly_encoded_UUID_value(t *testing.T) { @@ -173,8 +169,8 @@ func TestClassID_UnmarshalJSON_badInput_badly_encoded_UUID_value(t *testing.T) { var actual ClassID err := actual.UnmarshalJSON([]byte(tv)) - assert.EqualError(t, err, "bad UUID: invalid UUID length: 9") - assert.Equal(t, ClassIDTypeUnknown, actual.Type()) + assert.EqualError(t, err, "cannot unmarshal class id: bad UUID: invalid UUID length: 9") + assert.Equal(t, "", actual.Type()) } func TestClassID_SetOID_ok(t *testing.T) { @@ -200,8 +196,7 @@ func TestClassID_SetOID_ok(t *testing.T) { } for _, tv := range tvs { - c := ClassID{} - assert.NotNil(t, c.SetOID(tv)) + c := MustNewOIDClassID(tv) assert.Equal(t, tv, c.String()) } } @@ -219,7 +214,230 @@ func TestClassID_SetOID_bad(t *testing.T) { } for _, tv := range tvs { - c := ClassID{} - assert.Nil(t, c.SetOID(tv)) + c, err := NewOIDClassID(tv) + assert.NotNil(t, err) + assert.Nil(t, c) + } +} + +func Test_NewImplIDClassID(t *testing.T) { + classID, err := NewImplIDClassID(nil) + expected := [32]byte{} + require.NoError(t, err) + assert.Equal(t, expected[:], classID.Bytes()) + + taggedImplID := TaggedImplID(TestImplID) + + for _, v := range []any{ + TestImplID, + &TestImplID, + taggedImplID, + &taggedImplID, + taggedImplID.Bytes(), + } { + classID, err = NewImplIDClassID(v) + require.NoError(t, err) + assert.Equal(t, taggedImplID.Bytes(), classID.Bytes()) + } + + expected = [32]byte{ + 0x61, 0x63, 0x6d, 0x65, 0x2d, 0x69, 0x6d, 0x70, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x64, 0x2d, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, + } + classID, err = NewImplIDClassID("YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=") + require.NoError(t, err) + assert.Equal(t, expected[:], classID.Bytes()) + + _, err = NewImplIDClassID(7) + assert.EqualError(t, err, "unexpected type for psa.impl-id: int") +} + +func Test_NewUUIDClassID(t *testing.T) { + classID, err := NewUUIDClassID(nil) + + expected := [16]byte{} + require.NoError(t, err) + assert.Equal(t, expected[:], classID.Bytes()) + + taggedUUID := TaggedUUID(TestUUID) + + for _, v := range []any{ + TestUUID, + &TestUUID, + taggedUUID, + &taggedUUID, + taggedUUID.Bytes(), + } { + classID, err = NewUUIDClassID(v) + require.NoError(t, err) + assert.Equal(t, taggedUUID.Bytes(), classID.Bytes()) + } + + classID, err = NewUUIDClassID(taggedUUID.String()) + require.NoError(t, err) + assert.Equal(t, taggedUUID.Bytes(), classID.Bytes()) +} + +func Test_NewOIDClassID(t *testing.T) { + classID, err := NewOIDClassID(nil) + + expected := []byte{} + require.NoError(t, err) + assert.Equal(t, expected, classID.Bytes()) + + var oid OID + require.NoError(t, oid.FromString(TestOID)) + taggedOID := TaggedOID(oid) + + for _, v := range []any{ + TestOID, + oid, + &oid, + taggedOID, + &taggedOID, + taggedOID.Bytes(), + } { + classID, err = NewOIDClassID(v) + require.NoError(t, err) + expected := taggedOID.Bytes() + got := classID.Bytes() + assert.Equal(t, expected, got) + } + + classID, err = NewOIDClassID(taggedOID.String()) + require.NoError(t, err) + assert.Equal(t, taggedOID.Bytes(), classID.Bytes()) +} + +func Test_NewIntClassID(t *testing.T) { + classID, err := NewIntClassID(nil) + require.NoError(t, err) + assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, classID.Bytes()) + + testInt := 7 + testInt64 := int64(7) + testUint64 := uint64(7) + + var testBytes [8]byte + binary.BigEndian.PutUint64(testBytes[:], testUint64) + + for _, v := range []any{ + testInt, + &testInt, + testInt64, + &testInt64, + testUint64, + &testUint64, + "7", + testBytes[:], + } { + classID, err = NewIntClassID(v) + require.NoError(t, err) + got := classID.Bytes() + assert.Equal(t, testBytes[:], got) + } +} + +func Test_TaggedInt(t *testing.T) { + val := TaggedInt(7) + assert.Equal(t, "7", val.String()) + assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, val.Bytes()) + assert.Equal(t, "int", val.Type()) + assert.NoError(t, val.Valid()) + + classID := ClassID{&val} + + bytes, err := em.Marshal(classID) + require.NoError(t, err) + assert.Equal(t, []byte{ + 0xd9, 0x02, 0x27, // tag 551 + 0x07, // int 7 + }, bytes) + + var out ClassID + err = dm.Unmarshal(bytes, &out) + require.NoError(t, err) + assert.Equal(t, classID, out) + + jsonBytes, err := json.Marshal(classID) + require.NoError(t, err) + assert.Equal(t, `{"type":"int","value":7}`, string(jsonBytes)) + + out = ClassID{} + err = json.Unmarshal(jsonBytes, &out) + require.NoError(t, err) + assert.Equal(t, classID, out) +} + +type testClassID [4]byte + +func newTestClassID(val any) (*ClassID, error) { + return &ClassID{&testClassID{0x74, 0x65, 0x73, 0x74}}, nil +} + +func (o testClassID) Bytes() []byte { + return o[:] +} + +func (o testClassID) Type() string { + return "test-class-id" +} + +func (o testClassID) String() string { + return "test" +} + +func (o testClassID) Valid() error { + return nil +} + +func (o testClassID) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} + +func (o *testClassID) UnmarshalJSON(data []byte) error { + var out string + if err := json.Unmarshal(data, &out); err != nil { + return err } + + if len(out) != 4 { + return fmt.Errorf("bad testClassID: decoded %d bytes, want 4", len(out)) + } + + copy((*o)[:], []byte(out)) + + return nil +} + +func Test_RegisterClassIDType(t *testing.T) { + err := RegisterClassIDType(99999, newTestClassID) + require.NoError(t, err) + + classID, err := newTestClassID(nil) + require.NoError(t, err) + + data, err := json.Marshal(classID) + require.NoError(t, err) + assert.Equal(t, string(data), `{"type":"test-class-id","value":"test"}`) + + var out ClassID + err = json.Unmarshal(data, &out) + require.NoError(t, err) + assert.Equal(t, classID.Bytes(), out.Bytes()) + + data, err = em.Marshal(classID) + require.NoError(t, err) + assert.Equal(t, data, []byte{ + 0xda, 0x0, 0x1, 0x86, 0x9f, // tag 99999 + 0x44, // bstr(4) + 0x74, 0x65, 0x73, 0x74, // "test" + }) + + var out2 ClassID + err = dm.Unmarshal(data, &out2) + require.NoError(t, err) + assert.Equal(t, classID.Bytes(), out2.Bytes()) } diff --git a/comid/comid.go b/comid/comid.go index 48eae969..5052cc0c 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -120,7 +120,7 @@ func (o *Comid) AddEntity(name string, regID *string, roles ...Role) *Comid { } e := Entity{ - EntityName: name, + EntityName: MustNewStringEntityName(name), RegID: uri, Roles: rs, } diff --git a/comid/cryptokey.go b/comid/cryptokey.go index 53d99df8..3133b95e 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -14,6 +14,8 @@ import ( "fmt" "github.com/fxamacker/cbor/v2" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" "github.com/veraison/go-cose" "github.com/veraison/swid" ) @@ -58,50 +60,12 @@ type CryptoKey struct { // specified crypto key type. For PKIX types, k must be a string. For COSE_Key, // k must be a []byte. For thumbprint types, k must be a swid.HashEntry. func NewCryptoKey(k any, typ string) (*CryptoKey, error) { - switch typ { - case PKIXBase64KeyType: - v, ok := k.(string) - if !ok { - return nil, fmt.Errorf("value must be a string; found %T", k) - } - return NewPKIXBase64Key(v) - case PKIXBase64CertType: - v, ok := k.(string) - if !ok { - return nil, fmt.Errorf("value must be a string; found %T", k) - } - return NewPKIXBase64Cert(v) - case PKIXBase64CertPathType: - v, ok := k.(string) - if !ok { - return nil, fmt.Errorf("value must be a string; found %T", k) - } - return NewPKIXBase64CertPath(v) - case COSEKeyType: - v, ok := k.([]byte) - if !ok { - return nil, fmt.Errorf("value must be a []byte; found %T", k) - } - return NewCOSEKey(v) - case ThumbprintType, CertThumbprintType, CertPathThumbprintType: - v, ok := k.(swid.HashEntry) - if !ok { - return nil, fmt.Errorf("value must be a swid.HashEntry; found %T", k) - } - switch typ { - case ThumbprintType: - return NewThumbprint(v) - case CertThumbprintType: - return NewCertThumbprint(v) - case CertPathThumbprintType: - return NewCertPathThumbprint(v) - default: - // Should never here because of the the outer case clause - panic(fmt.Sprintf("unexpected thumbprint type: %s", typ)) - } - default: + factory, ok := cryptoKeyValueRegister[typ] + if !ok { return nil, fmt.Errorf("unexpected CryptoKey type: %s", typ) } + + return factory(k) } // MustNewCryptoKey is the same as NewCryptoKey, but does not return an error, @@ -126,6 +90,11 @@ func (o CryptoKey) Valid() error { return o.Value.Valid() } +// Type returns the type of the CryptoKey value +func (o CryptoKey) Type() string { + return o.Value.Type() +} + // PublicKey returns a crypto.PublicKey constructed from the CryptoKey's // underlying value. This returns an error if the CryptoKey is one of the // thumbprint types. @@ -136,30 +105,14 @@ func (o CryptoKey) PublicKey() (crypto.PublicKey, error) { // MarshalJSON returns a []byte containing the JSON representation of the // CryptoKey. func (o CryptoKey) MarshalJSON() ([]byte, error) { - value := struct { - Type string `json:"type"` - Value string `json:"value"` - }{ - Value: o.Value.String(), - } - - switch o.Value.(type) { - case TaggedPKIXBase64Key: - value.Type = PKIXBase64KeyType - case TaggedPKIXBase64Cert: - value.Type = PKIXBase64CertType - case TaggedPKIXBase64CertPath: - value.Type = PKIXBase64CertPathType - case TaggedCOSEKey: - value.Type = COSEKeyType - case TaggedThumbprint: - value.Type = ThumbprintType - case TaggedCertThumbprint: - value.Type = CertThumbprintType - case TaggedCertPathThumbprint: - value.Type = CertPathThumbprintType - default: - return nil, fmt.Errorf("unexpected ICryptoKeyValue type: %T", o.Value) + valueBytes, err := json.Marshal(o.Value.String()) + if err != nil { + return nil, err + } + + value := encoding.TypeAndValue{ + Type: o.Value.Type(), + Value: valueBytes, } return json.Marshal(value) @@ -168,10 +121,7 @@ func (o CryptoKey) MarshalJSON() ([]byte, error) { // UnmarshalJSON populates the CryptoKey from the JSON representation inside // the provided []byte. func (o *CryptoKey) UnmarshalJSON(b []byte) error { - var value struct { - Type string `json:"type"` - Value string `json:"value"` - } + var value encoding.TypeAndValue if err := json.Unmarshal(b, &value); err != nil { return err @@ -181,36 +131,23 @@ func (o *CryptoKey) UnmarshalJSON(b []byte) error { return errors.New("key type not set") } - switch value.Type { - case PKIXBase64KeyType: - o.Value = TaggedPKIXBase64Key(value.Value) - case PKIXBase64CertType: - o.Value = TaggedPKIXBase64Cert(value.Value) - case PKIXBase64CertPathType: - o.Value = TaggedPKIXBase64CertPath(value.Value) - case COSEKeyType: - data, err := base64.StdEncoding.DecodeString(value.Value) - if err != nil { - return fmt.Errorf("base64 decode error: %w", err) - } - o.Value = TaggedCOSEKey(data) - case ThumbprintType, CertThumbprintType, CertPathThumbprintType: - he, err := swid.ParseHashEntry(value.Value) - if err != nil { - return fmt.Errorf("swid.HashEntry decode error: %w", err) - } - switch value.Type { - case ThumbprintType: - o.Value = TaggedThumbprint{digest{he}} - case CertThumbprintType: - o.Value = TaggedCertThumbprint{digest{he}} - case CertPathThumbprintType: - o.Value = TaggedCertPathThumbprint{digest{he}} - } - default: + factory, ok := cryptoKeyValueRegister[value.Type] + if !ok { return fmt.Errorf("unexpected ICryptoKeyValue type: %q", value.Type) } + var valueString string + if err := json.Unmarshal(value.Value, &valueString); err != nil { + return err + } + + k, err := factory(valueString) + if err != nil { + return err + } + + o.Value = k.Value + return o.Valid() } @@ -229,11 +166,8 @@ func (o *CryptoKey) UnmarshalCBOR(b []byte) error { // ICryptoKeyValue is the interface implemented by the concrete CryptoKey value // types. type ICryptoKeyValue interface { - // String returns the string representation of the ICryptoKeyValue. - String() string - // Valid returns an error if validation of the ICryptoKeyValue fails, - // or nil if it succeeds. - Valid() error + extensions.ITypeChoiceValue + // PublicKey returns a crypto.PublicKey constructed from the // ICryptoKeyValue's underlying value. This returns an error if the // ICryptoKeyValue is one of the thumbprint types. @@ -244,7 +178,12 @@ type ICryptoKeyValue interface { // https://www.rfc-editor.org/rfc/rfc7468#section-13 type TaggedPKIXBase64Key string -func NewPKIXBase64Key(s string) (*CryptoKey, error) { +func NewPKIXBase64Key(k any) (*CryptoKey, error) { + s, ok := k.(string) + if !ok { + return nil, fmt.Errorf("value must be a string; found %T", k) + } + key := TaggedPKIXBase64Key(s) if err := key.Valid(); err != nil { return nil, err @@ -252,8 +191,8 @@ func NewPKIXBase64Key(s string) (*CryptoKey, error) { return &CryptoKey{key}, nil } -func MustNewPKIXBase64Key(s string) *CryptoKey { - key, err := NewPKIXBase64Key(s) +func MustNewPKIXBase64Key(k any) *CryptoKey { + key, err := NewPKIXBase64Key(k) if err != nil { panic(err) } @@ -269,6 +208,10 @@ func (o TaggedPKIXBase64Key) Valid() error { return err } +func (o TaggedPKIXBase64Key) Type() string { + return PKIXBase64KeyType +} + func (o TaggedPKIXBase64Key) PublicKey() (crypto.PublicKey, error) { if string(o) == "" { return nil, errors.New("key value not set") @@ -302,7 +245,12 @@ func (o TaggedPKIXBase64Key) PublicKey() (crypto.PublicKey, error) { // certificate. See https://www.rfc-editor.org/rfc/rfc7468#section-5 type TaggedPKIXBase64Cert string -func NewPKIXBase64Cert(s string) (*CryptoKey, error) { +func NewPKIXBase64Cert(k any) (*CryptoKey, error) { + s, ok := k.(string) + if !ok { + return nil, fmt.Errorf("value must be a string; found %T", k) + } + cert := TaggedPKIXBase64Cert(s) if err := cert.Valid(); err != nil { return nil, err @@ -310,8 +258,8 @@ func NewPKIXBase64Cert(s string) (*CryptoKey, error) { return &CryptoKey{cert}, nil } -func MustNewPKIXBase64Cert(s string) *CryptoKey { - cert, err := NewPKIXBase64Cert(s) +func MustNewPKIXBase64Cert(k any) *CryptoKey { + cert, err := NewPKIXBase64Cert(k) if err != nil { panic(err) } @@ -327,6 +275,10 @@ func (o TaggedPKIXBase64Cert) Valid() error { return err } +func (o TaggedPKIXBase64Cert) Type() string { + return PKIXBase64CertType +} + func (o TaggedPKIXBase64Cert) PublicKey() (crypto.PublicKey, error) { cert, err := o.cert() if err != nil { @@ -375,7 +327,11 @@ func (o TaggedPKIXBase64Cert) cert() (*x509.Certificate, error) { // directly certifies the one preceding. type TaggedPKIXBase64CertPath string -func NewPKIXBase64CertPath(s string) (*CryptoKey, error) { +func NewPKIXBase64CertPath(k any) (*CryptoKey, error) { + s, ok := k.(string) + if !ok { + return nil, fmt.Errorf("value must be a string; found %T", k) + } cert := TaggedPKIXBase64CertPath(s) if err := cert.Valid(); err != nil { @@ -385,8 +341,8 @@ func NewPKIXBase64CertPath(s string) (*CryptoKey, error) { return &CryptoKey{cert}, nil } -func MustNewPKIXBase64CertPath(s string) *CryptoKey { - cert, err := NewPKIXBase64CertPath(s) +func MustNewPKIXBase64CertPath(k any) *CryptoKey { + cert, err := NewPKIXBase64CertPath(k) if err != nil { panic(err) @@ -404,6 +360,10 @@ func (o TaggedPKIXBase64CertPath) Valid() error { return err } +func (o TaggedPKIXBase64CertPath) Type() string { + return PKIXBase64CertPathType +} + func (o TaggedPKIXBase64CertPath) PublicKey() (crypto.PublicKey, error) { certs, err := o.certPath() if err != nil { @@ -468,7 +428,22 @@ func (o TaggedPKIXBase64CertPath) certPath() ([]*x509.Certificate, error) { // https://www.rfc-editor.org/rfc/rfc9052#section-7 type TaggedCOSEKey []byte -func NewCOSEKey(b []byte) (*CryptoKey, error) { +func NewCOSEKey(k any) (*CryptoKey, error) { + var b []byte + var err error + + switch t := k.(type) { + case []byte: + b = t + case string: + b, err = base64.StdEncoding.DecodeString(t) + if err != nil { + return nil, fmt.Errorf("base64 decode error: %w", err) + } + default: + return nil, fmt.Errorf("value must be a []byte or a string; found %T", k) + } + key := TaggedCOSEKey(b) if err := key.Valid(); err != nil { @@ -478,8 +453,8 @@ func NewCOSEKey(b []byte) (*CryptoKey, error) { return &CryptoKey{key}, nil } -func MustNewCOSEKey(b []byte) *CryptoKey { - key, err := NewCOSEKey(b) +func MustNewCOSEKey(k any) *CryptoKey { + key, err := NewCOSEKey(k) if err != nil { panic(err) @@ -509,6 +484,10 @@ func (o TaggedCOSEKey) Valid() error { return err } +func (o TaggedCOSEKey) Type() string { + return COSEKeyType +} + func (o TaggedCOSEKey) PublicKey() (crypto.PublicKey, error) { if len(o) == 0 { return nil, errors.New("empty COSE_Key value") @@ -608,7 +587,22 @@ type TaggedThumbprint struct { digest } -func NewThumbprint(he swid.HashEntry) (*CryptoKey, error) { +func NewThumbprint(k any) (*CryptoKey, error) { + var he swid.HashEntry + var err error + + switch t := k.(type) { + case string: + he, err = swid.ParseHashEntry(t) + if err != nil { + return nil, fmt.Errorf("swid.HashEntry decode error: %w", err) + } + case swid.HashEntry: + he = t + default: + return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) + } + key := &CryptoKey{TaggedThumbprint{digest{he}}} if err := key.Valid(); err != nil { @@ -618,8 +612,8 @@ func NewThumbprint(he swid.HashEntry) (*CryptoKey, error) { return key, nil } -func MustNewThumbprint(he swid.HashEntry) *CryptoKey { - key, err := NewThumbprint(he) +func MustNewThumbprint(k any) *CryptoKey { + key, err := NewThumbprint(k) if err != nil { panic(err) @@ -628,13 +622,32 @@ func MustNewThumbprint(he swid.HashEntry) *CryptoKey { return key } +func (o TaggedThumbprint) Type() string { + return ThumbprintType +} + // TaggedCertThumbprint represents a digest of a certificate. The digest value // may be used to find the certificate if contained in a lookup table. type TaggedCertThumbprint struct { digest } -func NewCertThumbprint(he swid.HashEntry) (*CryptoKey, error) { +func NewCertThumbprint(k any) (*CryptoKey, error) { + var he swid.HashEntry + var err error + + switch t := k.(type) { + case string: + he, err = swid.ParseHashEntry(t) + if err != nil { + return nil, fmt.Errorf("swid.HashEntry decode error: %w", err) + } + case swid.HashEntry: + he = t + default: + return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) + } + key := &CryptoKey{TaggedCertThumbprint{digest{he}}} if err := key.Valid(); err != nil { @@ -644,8 +657,8 @@ func NewCertThumbprint(he swid.HashEntry) (*CryptoKey, error) { return key, nil } -func MustNewCertThumbprint(he swid.HashEntry) *CryptoKey { - key, err := NewCertThumbprint(he) +func MustNewCertThumbprint(k any) *CryptoKey { + key, err := NewCertThumbprint(k) if err != nil { panic(err) @@ -654,6 +667,10 @@ func MustNewCertThumbprint(he swid.HashEntry) *CryptoKey { return key } +func (o TaggedCertThumbprint) Type() string { + return CertThumbprintType +} + // TaggedCertPathThumbprint represents a digest of a certification path. The // digest value may be used to find the certificate path if contained in a // lookup table. @@ -661,7 +678,22 @@ type TaggedCertPathThumbprint struct { digest } -func NewCertPathThumbprint(he swid.HashEntry) (*CryptoKey, error) { +func NewCertPathThumbprint(k any) (*CryptoKey, error) { + var he swid.HashEntry + var err error + + switch t := k.(type) { + case string: + he, err = swid.ParseHashEntry(t) + if err != nil { + return nil, fmt.Errorf("swid.HashEntry decode error: %w", err) + } + case swid.HashEntry: + he = t + default: + return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) + } + key := &CryptoKey{TaggedCertPathThumbprint{digest{he}}} if err := key.Valid(); err != nil { @@ -671,8 +703,8 @@ func NewCertPathThumbprint(he swid.HashEntry) (*CryptoKey, error) { return key, nil } -func MustNewCertPathThumbprint(he swid.HashEntry) *CryptoKey { - key, err := NewCertPathThumbprint(he) +func MustNewCertPathThumbprint(k any) *CryptoKey { + key, err := NewCertPathThumbprint(k) if err != nil { panic(err) @@ -680,3 +712,52 @@ func MustNewCertPathThumbprint(he swid.HashEntry) *CryptoKey { return key } + +func (o TaggedCertPathThumbprint) Type() string { + return CertPathThumbprintType +} + +// ICryptoKeyFactory defines the signature for the factory functions that may be +// registred using RegisterCryptoKeyType to provide a new implementation of the +// corresponding type choice. The factory function should create a new *CryptoKey +// with the underlying value created based on the provided input. The range of +// valid inputs is up to the specific type choice implementation, however it +// _must_ accept nil as one of the inputs, and return the Zero value for +// implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type ICryptoKeyFactory func(any) (*CryptoKey, error) + +var cryptoKeyValueRegister = map[string]ICryptoKeyFactory{ + // types defined by the core spec + PKIXBase64KeyType: NewPKIXBase64Key, + PKIXBase64CertType: NewPKIXBase64Cert, + PKIXBase64CertPathType: NewPKIXBase64CertPath, + COSEKeyType: NewCOSEKey, + ThumbprintType: NewThumbprint, + CertThumbprintType: NewCertThumbprint, + CertPathThumbprintType: NewCertPathThumbprint, +} + +// RegisterCryptoKeyType registers a new ICryptoKeyValue implementation +// (created by the provided ICryptoKeyFactory) under the specified type name +// and CBOR tag. +func RegisterCryptoKeyType(tag uint64, factory ICryptoKeyFactory) error { + + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Type() + if _, exists := cryptoKeyValueRegister[typ]; exists { + return fmt.Errorf("crypto key type with name %q already exists", typ) + } + + if err := registerCOMIDTag(tag, nilVal.Value); err != nil { + return err + } + + cryptoKeyValueRegister[typ] = factory + + return nil +} diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index 1c0812fc..8a53edeb 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -4,6 +4,7 @@ package comid import ( + "crypto" "encoding/base64" "encoding/json" "fmt" @@ -149,7 +150,7 @@ func Test_CryptoKey_NewCOSEKey(t *testing.T) { } func Test_CryptoKey_NewThumbprint(t *testing.T) { - type newKeyFunc func(swid.HashEntry) (*CryptoKey, error) + type newKeyFunc func(any) (*CryptoKey, error) for _, newFunc := range []newKeyFunc{ NewThumbprint, @@ -177,7 +178,7 @@ func Test_CryptoKey_NewThumbprint(t *testing.T) { assert.Contains(t, err.Error(), "length mismatch for hash algorithm") } - type mustNewKeyFunc func(swid.HashEntry) *CryptoKey + type mustNewKeyFunc func(any) *CryptoKey for _, mustNewFunc := range []mustNewKeyFunc{ MustNewThumbprint, @@ -271,7 +272,7 @@ func Test_CryptoKey_UnmarshalJSON_negative(t *testing.T) { }, { Val: `{"value":"deadbeef"}`, - ErrMsg: "key type not set", + ErrMsg: "type not set", }, { Val: `{"type": "cose-key", "value":";;;"}`, @@ -371,22 +372,22 @@ func Test_NewCryptoKey_negative(t *testing.T) { { Type: COSEKeyType, In: 7, - ErrMsg: "value must be a []byte; found int", + ErrMsg: "value must be a []byte or a string; found int", }, { Type: ThumbprintType, In: 7, - ErrMsg: "value must be a swid.HashEntry; found int", + ErrMsg: "value must be a swid.HashEntry or a string; found int", }, { Type: CertThumbprintType, In: 7, - ErrMsg: "value must be a swid.HashEntry; found int", + ErrMsg: "value must be a swid.HashEntry or a string; found int", }, { Type: CertPathThumbprintType, In: 7, - ErrMsg: "value must be a swid.HashEntry; found int", + ErrMsg: "value must be a swid.HashEntry or a string; found int", }, { Type: "random-key", @@ -399,3 +400,55 @@ func Test_NewCryptoKey_negative(t *testing.T) { assert.ErrorContains(t, err, tv.ErrMsg) } } + +type testCryptoKey [4]byte + +func newTestCryptoKey(val any) (*CryptoKey, error) { + return &CryptoKey{&testCryptoKey{0x74, 0x64, 0x73, 0x74}}, nil +} + +func (o testCryptoKey) PublicKey() (crypto.PublicKey, error) { + return crypto.PublicKey(o[:]), nil +} + +func (o testCryptoKey) Type() string { + return "test-crypto-key" +} + +func (o testCryptoKey) String() string { + return "test" +} + +func (o testCryptoKey) Valid() error { + return nil +} + +func Test_RegisterCryptoKey(t *testing.T) { + err := RegisterCryptoKeyType(99998, newTestCryptoKey) + require.NoError(t, err) + + key, err := newTestCryptoKey(nil) + require.NoError(t, err) + + data, err := json.Marshal(key) + require.NoError(t, err) + assert.Equal(t, string(data), `{"type":"test-crypto-key","value":"test"}`) + + var out CryptoKey + err = json.Unmarshal(data, &out) + require.NoError(t, err) + assert.EqualValues(t, key, &out) + + data, err = em.Marshal(key) + require.NoError(t, err) + assert.Equal(t, data, []byte{ + 0xda, 0x0, 0x1, 0x86, 0x9e, // tag 99998 + 0x44, // bstr(4) + 0x74, 0x64, 0x73, 0x74, // "test" + }) + + var out2 CryptoKey + err = dm.Unmarshal(data, &out2) + require.NoError(t, err) + assert.Equal(t, key, &out2) +} diff --git a/comid/devidentitykey_test.go b/comid/devidentitykey_test.go index 14f2f6e1..b12f6a41 100644 --- a/comid/devidentitykey_test.go +++ b/comid/devidentitykey_test.go @@ -23,12 +23,12 @@ func TestDevIdentityKey_Valid_empty(t *testing.T) { testerr: "environment validation failed: environment must not be empty", }, { - env: Environment{Instance: NewInstanceUEID(TestUEID)}, + env: Environment{Instance: MustNewUEIDInstance(TestUEID)}, verifkey: CryptoKeys{}, testerr: "verification keys validation failed: no keys to validate", }, { - env: Environment{Instance: NewInstanceUEID(TestUEID)}, + env: Environment{Instance: MustNewUEIDInstance(TestUEID)}, verifkey: CryptoKeys{&invalidKey}, testerr: "verification keys validation failed: invalid key at index 0: key value not set", }, diff --git a/comid/entity.go b/comid/entity.go index 70399a7e..911c2111 100644 --- a/comid/entity.go +++ b/comid/entity.go @@ -4,23 +4,20 @@ package comid import ( + "encoding/json" + "errors" "fmt" + "unicode/utf8" "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" ) -type TaggedURI string - -func (o TaggedURI) Empty() bool { - return o == "" -} - // Entity stores an entity-map capable of CBOR and JSON serializations. type Entity struct { - EntityName string `cbor:"0,keyasint" json:"name"` - RegID *TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` - Roles Roles `cbor:"2,keyasint" json:"roles"` + EntityName *EntityName `cbor:"0,keyasint" json:"name"` + RegID *TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` + Roles Roles `cbor:"2,keyasint" json:"roles"` Extensions } @@ -41,7 +38,7 @@ func (o *Entity) SetEntityName(name string) *Entity { if name == "" { return nil } - o.EntityName = name + o.EntityName = MustNewStringEntityName(name) } return o } @@ -68,10 +65,14 @@ func (o *Entity) SetRoles(roles ...Role) *Entity { // Valid checks for validity of the fields within each Entity func (o Entity) Valid() error { - if o.EntityName == "" { + if o.EntityName == nil { return fmt.Errorf("invalid entity: empty entity-name") } + if err := o.EntityName.Valid(); err != nil { + return fmt.Errorf("invalid entity: %w", err) + } + if o.RegID != nil && o.RegID.Empty() { return fmt.Errorf("invalid entity: empty reg-id") } @@ -128,3 +129,219 @@ func (o Entities) Valid() error { } return nil } + +// EntityName encapsulates the name of the associated Entity. The CoRIM +// specification only allows for text (string) name, but this may be extended +// by other specifications. +type EntityName struct { + Value IEntityNameValue +} + +// NewEntityName creates a new EntityName of the specified type using the +// provided value. +func NewEntityName(val any, typ string) (*EntityName, error) { + factory, ok := entityNameValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unexpected entity name type: %s", typ) + } + + return factory(val) +} + +// MustNewEntityName is like NewEntityName, except it doesn't return an error, +// assuming that the provided value is valid. It panics if that isn't the case. +func MustNewEntityName(val any, typ string) *EntityName { + ret, err := NewEntityName(val, typ) + if err != nil { + panic(err) + } + + return ret +} + +func (o EntityName) String() string { + return o.Value.String() +} + +func (o EntityName) Valid() error { + if o.Value == nil { + return errors.New("empty entity name") + } + + return o.Value.Valid() +} + +func (o EntityName) MarshalCBOR() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + return em.Marshal(o.Value) +} + +func (o *EntityName) UnmarshalCBOR(data []byte) error { + if len(data) == 0 { + return errors.New("empty") + } + + majorType := (data[0] & 0xe0) >> 5 + if majorType == 3 { // text string + var text string + + if err := dm.Unmarshal(data, &text); err != nil { + return err + } + + name := StringEntityName(text) + o.Value = &name + + return nil + } + + return dm.Unmarshal(data, &o.Value) +} + +func (o EntityName) MarshalJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + if o.Value.Type() == extensions.StringType { + return json.Marshal(o.Value.String()) + } + + return extensions.TypeChoiceValueMarshalJSON(o.Value) +} + +func (o *EntityName) UnmarshalJSON(data []byte) error { + var text string + if err := json.Unmarshal(data, &text); err == nil { + *o = *MustNewStringEntityName(text) + return nil + } + + var tnv encoding.TypeAndValue + + if err := json.Unmarshal(data, &tnv); err != nil { + return fmt.Errorf("entity name decoding failure: %w", err) + } + + decoded, err := NewEntityName(nil, tnv.Type) + if err != nil { + return err + } + + if err := json.Unmarshal(tnv.Value, &decoded.Value); err != nil { + return fmt.Errorf( + "cannot unmarshal entity name: %w", + err, + ) + } + + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value + + return nil +} + +type IEntityNameValue interface { + extensions.ITypeChoiceValue +} + +type StringEntityName string + +func NewStringEntityName(val any) (*EntityName, error) { + var ret StringEntityName + + if val == nil { + ret = StringEntityName("") + return &EntityName{&ret}, nil + } + + switch t := val.(type) { + case string: + ret = StringEntityName(t) + case []byte: + if !utf8.Valid(t) { + return nil, errors.New("bytes do not form a valid UTF-8 string") + } + + ret = StringEntityName(t) + default: + return nil, fmt.Errorf("unexpected type for string entity name: %T", t) + } + + return &EntityName{&ret}, nil +} + +func MustNewStringEntityName(val any) *EntityName { + ret, err := NewStringEntityName(val) + if err != nil { + panic(err) + } + + return ret +} + +func (o StringEntityName) String() string { + return string(o) +} + +func (o StringEntityName) Type() string { + return extensions.StringType +} + +func (o StringEntityName) Valid() error { + if o == "" { + return errors.New("empty entity-name") + } + + return nil +} + +// IEntityNameFactory defines the signature for the factory functions that may +// be registred using RegisterEntityNameType to provide a new implementation of +// the corresponding type choice. The factory function should create a new +// *EntityName with the underlying value created based on the provided input. +// The range of valid inputs is up to the specific type choice implementation, +// however it _must_ accept nil as one of the inputs, and return the Zero value +// for implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IEntityNameFactory func(any) (*EntityName, error) + +var entityNameValueRegister = map[string]IEntityNameFactory{ + extensions.StringType: NewStringEntityName, +} + +// RegisterEntityNameType registers a new IEntityNameValue implementation +// (created by the provided IEntityNameFactory) under the specified type name +// and CBOR tag. +func RegisterEntityNameType(tag uint64, factory IEntityNameFactory) error { + + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Value.Type() + if _, exists := entityNameValueRegister[typ]; exists { + return fmt.Errorf("entity name type with name %q already exists", typ) + } + + if err := registerCOMIDTag(tag, nilVal.Value); err != nil { + return err + } + + entityNameValueRegister[typ] = factory + + return nil +} + +type TaggedURI string + +func (o TaggedURI) Empty() bool { + return o == "" +} diff --git a/comid/entity_test.go b/comid/entity_test.go index 61c0d1b3..d634a2f4 100644 --- a/comid/entity_test.go +++ b/comid/entity_test.go @@ -4,6 +4,8 @@ package comid import ( + "errors" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -83,3 +85,185 @@ func TestEntity_SetRegID_empty(t *testing.T) { assert.Nil(t, e.SetRegID("")) } + +type testEntityName uint64 + +func newTestEntityName(val any) (*EntityName, error) { + if val == nil { + v := testEntityName(0) + return &EntityName{&v}, nil + } + + u, ok := val.(uint64) + if !ok { + return nil, errors.New("must be uint64") + } + + v := testEntityName(u) + return &EntityName{&v}, nil +} + +func (o testEntityName) Type() string { + return "test" +} + +func (o testEntityName) String() string { + return fmt.Sprint(uint64(o)) +} + +func (o testEntityName) Valid() error { + return nil +} + +type testEntityNameBadType struct { + testEntityName +} + +func newTestEntityNameBadType(val any) (*EntityName, error) { + v := testEntityNameBadType{testEntityName(7)} + return &EntityName{&v}, nil +} + +func (o testEntityNameBadType) Type() string { + return "string" +} + +func Test_RegisterEntityNameType(t *testing.T) { + err := RegisterEntityNameType(32, newTestEntityName) + assert.EqualError(t, err, "tag 32 is already registered") + + err = RegisterEntityNameType(99994, newTestEntityNameBadType) + assert.EqualError(t, err, `entity name type with name "string" already exists`) + + registerTestEntityNameType(t) +} + +// Since there only one, untagged, entity name type in the core spec, we use +// the test type define above in order to test the marshaling code works +// properly. Since global environment is not reset when running multiple tests, +// we cannot simply call RegisterEntityNameType() inside each test that relies +// on the test type, as that will cause the "tag already registered" error. On +// the other hand, we do not want to create inter-test dependencies by relying +// that the test registering the type is run before the others that rely on it. +// To get around this, use this global flag to only register the test type if a +// previous test hasn't already done so. +var testEntityNameTypeRegistered = false + +func registerTestEntityNameType(t *testing.T) { + if !testEntityNameTypeRegistered { + err := RegisterEntityNameType(99994, newTestEntityName) + require.NoError(t, err) + + testEntityNameTypeRegistered = true + } +} + +func TestEntityName_CBOR(t *testing.T) { + registerTestEntityNameType(t) + + for _, tv := range []struct { + Value any + Type string + ExpectedBytes []byte + ExpectedString string + }{ + { + Value: "test", + Type: "string", + ExpectedBytes: []byte{ + 0x64, // tstr(4) + 0x74, 0x65, 0x73, 0x74, // "test" + }, + ExpectedString: "test", + }, + { + Value: uint64(7), + Type: "test", + ExpectedBytes: []byte{ + 0xda, 0x0, 0x1, 0x86, 0x9a, // tag 99994 + 0x07, // unsigned int(7) + }, + ExpectedString: "7", + }, + } { + t.Run(tv.Type, func(t *testing.T) { + en, err := NewEntityName(tv.Value, tv.Type) + require.NoError(t, err) + + data, err := en.MarshalCBOR() + require.NoError(t, err) + + assert.Equal(t, tv.ExpectedBytes, data) + + var out EntityName + + err = out.UnmarshalCBOR(data) + require.NoError(t, err) + + assert.Equal(t, tv.ExpectedString, out.String()) + }) + } +} + +func TestEntityName_JSON(t *testing.T) { + registerTestEntityNameType(t) + + for _, tv := range []struct { + Value any + Type string + ExpectedBytes []byte + ExpectedString string + }{ + { + Value: "test", + Type: "string", + ExpectedBytes: []byte(`"test"`), + ExpectedString: "test", + }, + { + Value: uint64(7), + Type: "test", + ExpectedBytes: []byte(`{"type":"test","value":7}`), + ExpectedString: "7", + }, + } { + t.Run(tv.Type, func(t *testing.T) { + en, err := NewEntityName(tv.Value, tv.Type) + require.NoError(t, err) + + data, err := en.MarshalJSON() + require.NoError(t, err) + + assert.Equal(t, tv.ExpectedBytes, data) + + var out EntityName + + err = out.UnmarshalJSON(data) + require.NoError(t, err) + + assert.Equal(t, tv.ExpectedString, out.String()) + }) + } +} + +func Test_NewStringEntityName(t *testing.T) { + out, err := NewStringEntityName(nil) + require.NoError(t, err) + assert.EqualError(t, out.Valid(), "empty entity-name") + + out, err = NewStringEntityName([]byte("test")) + require.NoError(t, err) + assert.Equal(t, "test", out.String()) + + _, err = NewStringEntityName(7) + assert.EqualError(t, err, "unexpected type for string entity name: int") +} + +func Test_MustNewEntityName(t *testing.T) { + out := MustNewEntityName("test", "string") + assert.Equal(t, "test", out.String()) + + assert.Panics(t, func() { + MustNewEntityName(7, "int") + }) +} diff --git a/comid/environment_test.go b/comid/environment_test.go index c078ce90..f33d1ebf 100644 --- a/comid/environment_test.go +++ b/comid/environment_test.go @@ -46,7 +46,7 @@ func TestEnvironment_Valid_empty_group(t *testing.T) { err := tv.Valid() - assert.EqualError(t, err, "group validation failed: invalid group id") + assert.EqualError(t, err, "group validation failed: no value set") } func TestEnvironment_Valid_ok_with_class(t *testing.T) { tv := Environment{ @@ -78,7 +78,7 @@ func TestEnvironment_ToCBOR_class_only(t *testing.T) { func TestEnvironment_ToCBOR_class_and_instance(t *testing.T) { tv := Environment{ Class: NewClassUUID(TestUUID), - Instance: NewInstanceUEID(TestUEID), + Instance: MustNewUEIDInstance(TestUEID), } require.NotNil(t, tv.Class) require.NotNil(t, tv.Instance) @@ -96,7 +96,7 @@ func TestEnvironment_ToCBOR_class_and_instance(t *testing.T) { func TestEnvironment_ToCBOR_instance_only(t *testing.T) { tv := Environment{ - Instance: NewInstanceUEID(TestUEID), + Instance: MustNewUEIDInstance(TestUEID), } require.NotNil(t, tv.Instance) @@ -113,7 +113,7 @@ func TestEnvironment_ToCBOR_instance_only(t *testing.T) { func TestEnvironment_ToCBOR_group_only(t *testing.T) { tv := Environment{ - Group: NewGroupUUID(TestUUID), + Group: MustNewUUIDGroup(TestUUID), } require.NotNil(t, tv.Group) @@ -180,7 +180,7 @@ func TestEnvironment_FromCBOR_class_and_instance(t *testing.T) { assert.NotNil(t, actual.Class) assert.Equal(t, TestUUIDString, actual.Class.ClassID.String()) assert.NotNil(t, actual.Instance) - assert.Equal(t, TestUEIDString, actual.Instance.String()) + assert.Equal(t, []byte(TestUEID), actual.Instance.Bytes()) assert.Nil(t, actual.Group) } @@ -197,3 +197,25 @@ func TestEnvironment_FromCBOR_group_only(t *testing.T) { assert.NotNil(t, actual.Group) assert.Equal(t, TestUUIDString, actual.Group.String()) } + +func TestEnviroment_JSON(t *testing.T) { + testEnv := Environment{ + Class: NewClassUUID(TestUUID), + } + + out, err := testEnv.ToJSON() + require.NoError(t, err) + assert.Equal(t, `{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}}`, string(out)) + + var outEnv Environment + + err = outEnv.FromJSON(out) + require.NoError(t, err) + assert.Equal(t, testEnv, outEnv) + + _, err = Environment{}.ToJSON() + assert.EqualError(t, err, "environment must not be empty") + + err = outEnv.FromJSON([]byte(`{"class": 7}`)) + assert.EqualError(t, err, "json: cannot unmarshal number into Go struct field Environment.class of type comid.Class") +} diff --git a/comid/example_cca_refval_test.go b/comid/example_cca_refval_test.go index a9d1102b..9b52304c 100644 --- a/comid/example_cca_refval_test.go +++ b/comid/example_cca_refval_test.go @@ -66,21 +66,23 @@ func extractCCARefVal(rv ReferenceValue) error { if !m.Key.IsSet() { return fmt.Errorf("mKey not set at index %d", i) } - if m.Key.IsPSARefValID() { + + switch t := m.Key.Value.(type) { + case *TaggedPSARefValID: if err := extractSwMeasurement(m); err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } - } - if m.Key.IsCCAPlatformConfigID() { + case *TaggedCCAPlatformConfigID: if err := extractCCARefValID(m.Key); err != nil { return fmt.Errorf("extracting cca-refval-id: %w", err) } if err := extractRawValue(m.Val.RawValue); err != nil { return fmt.Errorf("extracting raw vlue: %w", err) } - - return nil + default: + return fmt.Errorf("unexpected Mkey type: %T", t) } + } return nil @@ -105,9 +107,9 @@ func extractCCARefValID(k *Mkey) error { return fmt.Errorf("no measurement key") } - id, err := k.GetCCAPlatformConfigID() - if err != nil { - return fmt.Errorf("getting CCA platform config id: %w", err) + id, ok := k.Value.(*TaggedCCAPlatformConfigID) + if !ok { + return fmt.Errorf("expected CCA platform config id, found: %T", k.Value) } fmt.Printf("Label: %s\n", id) return nil diff --git a/comid/example_psa_keys_test.go b/comid/example_psa_keys_test.go index edf95989..3780cbbd 100644 --- a/comid/example_psa_keys_test.go +++ b/comid/example_psa_keys_test.go @@ -70,12 +70,7 @@ func extractInstanceID(i *Instance) error { return fmt.Errorf("no instance") } - instID, err := i.GetUEID() - if err != nil { - return fmt.Errorf("extracting implemenetation-id: %w", err) - } - - fmt.Printf("InstanceID: %x\n", instID) + fmt.Printf("InstanceID: %x\n", i.Bytes()) return nil } diff --git a/comid/example_psa_refval_test.go b/comid/example_psa_refval_test.go index 230209d0..18817d91 100644 --- a/comid/example_psa_refval_test.go +++ b/comid/example_psa_refval_test.go @@ -111,9 +111,10 @@ func extractPSARefValID(k *Mkey) error { return fmt.Errorf("no measurement key") } - id, err := k.GetPSARefValID() - if err != nil { - return fmt.Errorf("getting PSA refval id: %w", err) + id, ok := k.Value.(*TaggedPSARefValID) + + if !ok { + return fmt.Errorf("expected PSA refval id, found: %T", k.Value) } fmt.Printf("SignerID: %x\n", id.SignerID) @@ -142,12 +143,11 @@ func extractImplementationID(c *Class) error { return fmt.Errorf("no class-id") } - implID, err := classID.GetImplID() - if err != nil { - return fmt.Errorf("extracting implemenetation-id: %w", err) + if classID.Type() != ImplIDType { + return fmt.Errorf("class id is not a psa.impl-id") } - fmt.Printf("ImplementationID: %x\n", implID) + fmt.Printf("ImplementationID: %x\n", classID.Bytes()) return nil } diff --git a/comid/example_test.go b/comid/example_test.go index 756829d3..9d3985c6 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -26,13 +26,12 @@ func Example_encode() { SetModel("RoadRunner"). SetLayer(0). SetIndex(1), - Instance: NewInstanceUEID(TestUEID), - Group: NewGroupUUID(TestUUID), + Instance: MustNewUEIDInstance(TestUEID), + Group: MustNewUUIDGroup(TestUUID), }, Measurements: *NewMeasurements(). AddMeasurement( - NewMeasurement(). - SetKeyUUID(TestUUID). + MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetSVN(2). AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). @@ -55,13 +54,12 @@ func Example_encode() { SetModel("RoadRunner"). SetLayer(0). SetIndex(1), - Instance: NewInstanceUEID(TestUEID), - Group: NewGroupUUID(TestUUID), + Instance: MustNewUEIDInstance(TestUEID), + Group: MustNewUUIDGroup(TestUUID), }, Measurements: *NewMeasurements(). AddMeasurement( - NewMeasurement(). - SetKeyUUID(TestUUID). + MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetMinSVN(2). AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). @@ -79,7 +77,7 @@ func Example_encode() { AddAttestVerifKey( AttestVerifKey{ Environment: Environment{ - Instance: NewInstanceUUID(uuid.UUID(TestUUID)), + Instance: MustNewUUIDInstance(uuid.UUID(TestUUID)), }, VerifKeys: *NewCryptoKeys(). Add( @@ -89,7 +87,7 @@ func Example_encode() { ).AddDevIdentityKey( DevIdentityKey{ Environment: Environment{ - Instance: NewInstanceUEID(TestUEID), + Instance: MustNewUEIDInstance(TestUEID), }, VerifKeys: *NewCryptoKeys(). Add( @@ -110,7 +108,7 @@ func Example_encode() { // Output: // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA() { @@ -126,25 +124,23 @@ func Example_encode_PSA() { }, Measurements: *NewMeasurements(). AddMeasurement( - NewPSAMeasurement( - *NewPSARefValID(TestSignerID). - SetLabel("BL"). - SetVersion("5.0.5"), - ).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), + MustNewPSAMeasurement( + MustCreatePSARefValID( + TestSignerID, "BL", "5.0.5", + )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), ). AddMeasurement( - NewPSAMeasurement( - *NewPSARefValID(TestSignerID). - SetLabel("PRoT"). - SetVersion("1.3.5"), - ).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), + MustNewPSAMeasurement( + MustCreatePSARefValID( + TestSignerID, "PRoT", "1.3.5", + )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), ), }, ). AddAttestVerifKey( AttestVerifKey{ Environment: Environment{ - Instance: NewInstanceUEID(TestUEID), + Instance: MustNewUEIDInstance(TestUEID), }, VerifKeys: *NewCryptoKeys(). Add( @@ -175,7 +171,7 @@ func Example_encode_PSA_attestation_verification() { AddAttestVerifKey( AttestVerifKey{ Environment: Environment{ - Instance: NewInstanceUEID(TestUEID), + Instance: MustNewUEIDInstance(TestUEID), }, VerifKeys: *NewCryptoKeys(). Add( diff --git a/comid/group.go b/comid/group.go index adb9cdb4..789ed197 100644 --- a/comid/group.go +++ b/comid/group.go @@ -5,66 +5,51 @@ package comid import ( "encoding/json" + "errors" "fmt" + + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" ) // Group stores a group identity. The supported format is UUID. type Group struct { - val interface{} + Value IGroupValue } // NewGroup instantiates an empty group -func NewGroup() *Group { - return &Group{} -} - -// SetUUID sets the identity of the target group to the supplied UUID -func (o *Group) SetUUID(val UUID) *Group { - if o != nil { - o.val = TaggedUUID(val) +func NewGroup(val any, typ string) (*Group, error) { + factory, ok := groupValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unknown group type: %s", typ) } - return o -} -// NewGroupUUID instantiates a new group with the supplied UUID identity -func NewGroupUUID(val UUID) *Group { - return NewGroup().SetUUID(val) + return factory(val) } // Valid checks for the validity of given group func (o Group) Valid() error { - if o.String() == "" { - return fmt.Errorf("invalid group id") + if o.Value == nil { + return errors.New("no value set") } - return nil + + return o.Value.Valid() } // String returns a printable string of the Group value. UUIDs use the // canonical 8-4-4-4-12 format, UEIDs are hex encoded. func (o Group) String() string { - switch t := o.val.(type) { - case TaggedUUID: - return UUID(t).String() - default: - return "" - } + return o.Value.String() } // MarshalCBOR serializes the target group to CBOR func (o Group) MarshalCBOR() ([]byte, error) { - return em.Marshal(o.val) + return em.Marshal(o.Value) } // UnmarshalCBOR deserializes the supplied CBOR into the target group func (o *Group) UnmarshalCBOR(data []byte) error { - var uuid TaggedUUID - - if dm.Unmarshal(data, &uuid) == nil { - o.val = uuid - return nil - } - - return fmt.Errorf("unknown group type (CBOR: %x)", data) + return dm.Unmarshal(data, &o.Value) } // UnmarshalJSON deserializes the supplied JSON type/value object into the Group @@ -75,43 +60,97 @@ func (o *Group) UnmarshalCBOR(data []byte) error { // "value": "69E027B2-7157-4758-BCB4-D9F167FE49EA" // } func (o *Group) UnmarshalJSON(data []byte) error { - var v tnv + var tnv encoding.TypeAndValue - if err := json.Unmarshal(data, &v); err != nil { + if err := json.Unmarshal(data, &tnv); err != nil { + return fmt.Errorf("group decoding failure: %w", err) + } + + decoded, err := NewGroup(nil, tnv.Type) + if err != nil { return err } - switch v.Type { - case "uuid": - var x UUID - if err := x.UnmarshalJSON(v.Value); err != nil { - return err - } - o.val = TaggedUUID(x) - default: - return fmt.Errorf("unknown type %s for group", v.Type) + if err := json.Unmarshal(tnv.Value, &decoded.Value); err != nil { + return fmt.Errorf( + "cannot unmarshal group: %w", + err, + ) } + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value + return nil } func (o Group) MarshalJSON() ([]byte, error) { - var ( - v tnv - b []byte - err error - ) - - switch t := o.val.(type) { - case TaggedUUID: - b, err = UUID(t).MarshalJSON() - if err != nil { - return nil, err - } - v = tnv{Type: "ueid", Value: b} - default: - return nil, fmt.Errorf("unknown type %T for group", t) + return extensions.TypeChoiceValueMarshalJSON(o.Value) +} + +type IGroupValue interface { + extensions.ITypeChoiceValue +} + +func NewUUIDGroup(val any) (*Group, error) { + if val == nil { + return &Group{&TaggedUUID{}}, nil + } + + u, err := NewTaggedUUID(val) + if err != nil { + return nil, err + } + + return &Group{u}, nil +} + +func MustNewUUIDGroup(val any) *Group { + ret, err := NewUUIDGroup(val) + if err != nil { + panic(err) + } + + return ret +} + +// IGroupFactory defines the signature for the factory functions that may be +// registred using RegisterGroupType to provide a new implementation of the +// corresponding type choice. The factory function should create a new *Group +// with the underlying value created based on the provided input. The range of +// valid inputs is up to the specific type choice implementation, however it +// _must_ accept nil as one of the inputs, and return the Zero value for +// implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IGroupFactory func(any) (*Group, error) + +var groupValueRegister = map[string]IGroupFactory{ + UUIDType: NewUUIDGroup, +} + +// RegisterGroupType registers a new IGroupValue implementation +// (created by the provided IGroupFactory) under the specified type name +// and CBOR tag. +func RegisterGroupType(tag uint64, factory IGroupFactory) error { + + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Value.Type() + if _, exists := groupValueRegister[typ]; exists { + return fmt.Errorf("Group type with name %q already exists", typ) } - return json.Marshal(v) + if err := registerCOMIDTag(tag, nilVal.Value); err != nil { + return err + } + + groupValueRegister[typ] = factory + + return nil } diff --git a/comid/group_test.go b/comid/group_test.go new file mode 100644 index 00000000..4363f3b2 --- /dev/null +++ b/comid/group_test.go @@ -0,0 +1,62 @@ +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testGroup uint64 + +func newTestGroup(val any) (*Group, error) { + v := testGroup(7) + return &Group{&v}, nil +} + +func (o testGroup) Type() string { + return "test-value" +} + +func (o testGroup) String() string { + return "test" +} + +func (o testGroup) Valid() error { + return nil +} + +type testGroupBadType struct { + testGroup +} + +func newTestGroupBadType(val any) (*Group, error) { + v := testGroupBadType{testGroup(7)} + return &Group{&v}, nil +} + +func (o testGroupBadType) Type() string { + return "uuid" +} + +func Test_RegisterGroupType(t *testing.T) { + err := RegisterGroupType(32, newTestGroup) + assert.EqualError(t, err, "tag 32 is already registered") + + err = RegisterGroupType(99993, newTestGroupBadType) + assert.EqualError(t, err, `Group type with name "uuid" already exists`) + + err = RegisterGroupType(99993, newTestGroup) + require.NoError(t, err) + +} + +func TestGroup_UmarshalJSON(t *testing.T) { + var group Group + + err := group.UnmarshalJSON([]byte(`{`)) + assert.EqualError(t, err, "group decoding failure: unexpected end of JSON input") + + err = group.UnmarshalJSON([]byte(`{"type":"uuid","value":"aaaa"}`)) + assert.EqualError(t, err, "cannot unmarshal group: bad UUID: invalid UUID length: 4") +} diff --git a/comid/instance.go b/comid/instance.go index 8201145d..83631b32 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -1,51 +1,27 @@ package comid import ( - "encoding/hex" "encoding/json" "fmt" - "github.com/google/uuid" - "github.com/veraison/eat" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" ) // Instance stores an instance identity. The supported formats are UUID and UEID. type Instance struct { - val interface{} + Value IInstanceValue } -// NewInstance instantiates an empty instance -func NewInstance() *Instance { - return &Instance{} -} - -// SetUEID sets the identity of the target instance to the supplied UEID -func (o *Instance) SetUEID(val eat.UEID) *Instance { - if o != nil { - if val.Validate() != nil { - return nil - } - o.val = TaggedUEID(val) +// NewInstance creates a new instance with the value of the specified type +// populated using the provided value. +func NewInstance(val any, typ string) (*Instance, error) { + factory, ok := instanceValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unknown instance type: %s", typ) } - return o -} -// SetUUID sets the identity of the target instance to the supplied UUID -func (o *Instance) SetUUID(val uuid.UUID) *Instance { - if o != nil { - o.val = TaggedUUID(val) - } - return o -} - -// NewInstanceUEID instantiates a new instance with the supplied UEID identity -func NewInstanceUEID(val eat.UEID) *Instance { - return NewInstance().SetUEID(val) -} - -// NewInstanceUUID instantiates a new instance with the supplied UUID identity -func NewInstanceUUID(val uuid.UUID) *Instance { - return NewInstance().SetUUID(val) + return factory(val) } // Valid checks for the validity of given instance @@ -56,124 +32,186 @@ func (o Instance) Valid() error { return nil } -func (o Instance) GetUEID() (eat.UEID, error) { - switch t := o.val.(type) { - case TaggedUEID: - return eat.UEID(t), nil - default: - return eat.UEID{}, fmt.Errorf("instance-id type is: %T", t) - } -} - -func (o Instance) GetUUID() (UUID, error) { - switch t := o.val.(type) { - case TaggedUUID: - return UUID(t), nil - default: - return UUID{}, fmt.Errorf("instance-id type is: %T", t) - } -} - // String returns a printable string of the Instance value. UUIDs use the // canonical 8-4-4-4-12 format, UEIDs are hex encoded. func (o Instance) String() string { - switch t := o.val.(type) { - case TaggedUUID: - return UUID(t).String() - case TaggedUEID: - return hex.EncodeToString(t) - default: + if o.Value == nil { return "" } + + return o.Value.String() +} + +// Type returns a string naming the type of the underlying Instance value. +func (o Instance) Type() string { + return o.Value.Type() +} + +// Bytes returns a []byte containing the bytes of the underlying Instance +// value. +func (o Instance) Bytes() []byte { + return o.Value.Bytes() } // MarshalCBOR serializes the target instance to CBOR func (o Instance) MarshalCBOR() ([]byte, error) { - return em.Marshal(o.val) + return em.Marshal(o.Value) } func (o *Instance) UnmarshalCBOR(data []byte) error { - var ueid TaggedUEID - - if dm.Unmarshal(data, &ueid) == nil { - o.val = ueid - return nil - } - - var u TaggedUUID - - if dm.Unmarshal(data, &u) == nil { - o.val = u - return nil - } - - return fmt.Errorf("unknown instance type (CBOR: %x)", data) + return dm.Unmarshal(data, &o.Value) } -// UnmarshalJSON deserializes the supplied JSON type/value object into the Group -// target. The supported formats are UUID, e.g.: +// UnmarshalJSON deserializes the supplied JSON object into the target Instance +// The instance object must have the following shape: // // { -// "type": "uuid", -// "value": "69E027B2-7157-4758-BCB4-D9F167FE49EA" +// "type": "", +// "value": // } // -// and UEID: +// where must be one of the known IInstanceValue implementation +// type names (available in the base implementation: "ueid" and "uuid"), and +// is the JSON encoding of the instance value. The exact +// encoding is dependent. For the base implmentation types it is // -// { -// "type": "ueid", -// "value": "Ad6tvu/erb7v3q2+796tvu8=" -// } +// ueid: base64-encoded bytes, e.g. "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" +// uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" func (o *Instance) UnmarshalJSON(data []byte) error { - var v tnv + var tnv encoding.TypeAndValue - if err := json.Unmarshal(data, &v); err != nil { + if err := json.Unmarshal(data, &tnv); err != nil { + return fmt.Errorf("instance decoding failure: %w", err) + } + + decoded, err := NewInstance(nil, tnv.Type) + if err != nil { return err } - switch v.Type { - case "uuid": - var x UUID - if err := x.UnmarshalJSON(v.Value); err != nil { - return err - } - o.val = TaggedUUID(x) - case "ueid": - var x UEID - if err := x.UnmarshalJSON(v.Value); err != nil { - return err - } - o.val = TaggedUEID(x) - default: - return fmt.Errorf("unknown type %s for instance", v.Type) + if err := json.Unmarshal(tnv.Value, &decoded.Value); err != nil { + return fmt.Errorf( + "cannot unmarshal instance: %w", + err, + ) } + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value + return nil } +// MarshalJSON serializes the Instance into a JSON object. func (o Instance) MarshalJSON() ([]byte, error) { - var ( - v tnv - b []byte - err error - ) - - switch t := o.val.(type) { - case TaggedUUID: - b, err = UUID(t).MarshalJSON() - if err != nil { - return nil, err - } - v = tnv{Type: "uuid", Value: b} - case TaggedUEID: - b, err = UEID(t).MarshalJSON() - if err != nil { - return nil, err - } - v = tnv{Type: "ueid", Value: b} - default: - return nil, fmt.Errorf("unknown type %T for instance", t) - } - - return json.Marshal(v) + valueBytes, err := json.Marshal(o.Value) + if err != nil { + return nil, err + } + + value := encoding.TypeAndValue{ + Type: o.Value.Type(), + Value: valueBytes, + } + + return json.Marshal(value) +} + +// IInstanceValue is the interface implemented by all Instance value +// implementations. +type IInstanceValue interface { + extensions.ITypeChoiceValue + + Bytes() []byte +} + +// NewUEIDInstance instantiates a new instance with the supplied UEID identity. +func NewUEIDInstance(val any) (*Instance, error) { + if val == nil { + return &Instance{&TaggedUEID{}}, nil + } + + ret, err := NewTaggedUEID(val) + if err != nil { + return nil, err + } + return &Instance{ret}, nil +} + +// MustNewUEIDInstance is like NewUEIDInstance execept it does not return an +// error, assuming that the provided value is valid. It panics if that isn't +// the case. +func MustNewUEIDInstance(val any) *Instance { + ret, err := NewUEIDInstance(val) + if err != nil { + panic(err) + } + + return ret +} + +// NewUUIDInstance instantiates a new instance with the supplied UUID identity +func NewUUIDInstance(val any) (*Instance, error) { + if val == nil { + return &Instance{&TaggedUUID{}}, nil + } + + ret, err := NewTaggedUUID(val) + if err != nil { + return nil, err + } + + return &Instance{ret}, nil +} + +// MustNewUUIDInstance is like NewUUIDInstance execept it does not return an +// error, assuming that the provided value is valid. It panics if that isn't +// the case. +func MustNewUUIDInstance(val any) *Instance { + ret, err := NewUUIDInstance(val) + if err != nil { + panic(err) + } + + return ret +} + +// IInstanceFactory defines the signature for the factory functions that may be +// registred using RegisterInstanceType to provide a new implementation of the +// corresponding type choice. The factory function should create a new *Instance +// with the underlying value created based on the provided input. The range of +// valid inputs is up to the specific type choice implementation, however it +// _must_ accept nil as one of the inputs, and return the Zero value for +// implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IInstanceFactory func(any) (*Instance, error) + +var instanceValueRegister = map[string]IInstanceFactory{ + UEIDType: NewUEIDInstance, + UUIDType: NewUUIDInstance, +} + +// RegisterInstanceType registers a new IInstanceValue implementation (created +// by the provided IInstanceFactory) under the specified CBOR tag. +func RegisterInstanceType(tag uint64, factory IInstanceFactory) error { + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Type() + if _, exists := instanceValueRegister[typ]; exists { + return fmt.Errorf("class ID type with name %q already exists", typ) + } + + if err := registerCOMIDTag(tag, nilVal.Value); err != nil { + return err + } + + instanceValueRegister[typ] = factory + + return nil } diff --git a/comid/instance_test.go b/comid/instance_test.go index 34e671e7..6ea22fd4 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -1,24 +1,69 @@ package comid import ( + "encoding/json" "testing" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestInstance_GetUUID_OK(t *testing.T) { - inst := NewInstanceUUID(uuid.UUID(TestUUID)) - require.NotNil(t, inst) - u, err := inst.GetUUID() - assert.Nil(t, err) - assert.Equal(t, u, TestUUID) + inst := MustNewUUIDInstance(TestUUID) + u, ok := inst.Value.(*TaggedUUID) + assert.True(t, ok) + assert.EqualValues(t, TestUUID, *u) } -func TestInstance_GetUUID_NOK(t *testing.T) { - inst := &Instance{} - expectedErr := "instance-id type is: " - _, err := inst.GetUUID() - assert.EqualError(t, err, expectedErr) +type testInstance string + +func newTestInstance(val any) (*Instance, error) { + ret := testInstance("test") + return &Instance{&ret}, nil +} + +func (o testInstance) Bytes() []byte { + return []byte(o) +} + +func (o testInstance) Type() string { + return "test-instance" +} + +func (o testInstance) String() string { + return string(o) +} + +func (o testInstance) Valid() error { + return nil +} + +func Test_RegisterInstanceType(t *testing.T) { + err := RegisterInstanceType(99997, newTestInstance) + require.NoError(t, err) + + instance, err := newTestInstance(nil) + require.NoError(t, err) + + data, err := json.Marshal(instance) + require.NoError(t, err) + assert.Equal(t, string(data), `{"type":"test-instance","value":"test"}`) + + var out Instance + err = json.Unmarshal(data, &out) + require.NoError(t, err) + assert.Equal(t, instance.Bytes(), out.Bytes()) + + data, err = em.Marshal(instance) + require.NoError(t, err) + assert.Equal(t, data, []byte{ + 0xda, 0x0, 0x1, 0x86, 0x9d, // tag 99997 + 0x64, // tstr(4) + 0x74, 0x65, 0x73, 0x74, // "test" + }) + + var out2 Instance + err = dm.Unmarshal(data, &out2) + require.NoError(t, err) + assert.Equal(t, instance.Bytes(), out2.Bytes()) } diff --git a/comid/measurement.go b/comid/measurement.go index 9ef993a6..f942855a 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -5,8 +5,10 @@ package comid import ( "encoding/json" + "errors" "fmt" "net" + "strconv" "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" @@ -16,198 +18,283 @@ import ( const MaxUint64 = ^uint64(0) -// Measurement stores a measurement-map with CBOR and JSON serializations. -type Measurement struct { - Key *Mkey `cbor:"0,keyasint,omitempty" json:"key,omitempty"` - Val Mval `cbor:"1,keyasint" json:"value"` - AuthorizedBy *CryptoKey `cbor:"2,keyasint,omitempty" json:"authorized-by,omitempty"` -} - // Mkey stores a $measured-element-type-choice. // The supported types are UUID, PSA refval-id, CCA platform-config-id and unsigned integer // TO DO Add tagged OID: see https://github.com/veraison/corim/issues/35 type Mkey struct { - val interface{} + Value IMKeyValue +} + +// NewMkey creates a new Mkey of the specfied type using the provided value. +func NewMkey(val any, typ string) (*Mkey, error) { + factory, ok := mkeyValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unexpected measurement key type: %q", typ) + } + + return factory(nil) +} + +// MustNewMkey is like NewMkey, execept it does not return an error, assuming +// that the provided value is valid. It panics if that is not the case. +func MustNewMkey(val any, typ string) *Mkey { + ret, err := NewMkey(val, typ) + if err != nil { + panic(err) + } + + return ret } +// IsSet returns true if the value of the Mkey is set. func (o Mkey) IsSet() bool { - return o.val != nil + return o.Value != nil } +// Valid returns nil if the Mkey is valid or an error describing the problem, +// if it is not. func (o Mkey) Valid() error { - switch t := o.val.(type) { - case TaggedUUID: - if UUID(t).Empty() { - return fmt.Errorf("empty UUID") - } - return nil - case TaggedPSARefValID: - return PSARefValID(t).Valid() - case TaggedCCAPlatformConfigID: - if CCAPlatformConfigID(t).Empty() { - return fmt.Errorf("empty CCAPlatformConfigID") - } - case uint64: - if o.val == nil { - return fmt.Errorf("empty uint Mkey") - } - return nil - default: - return fmt.Errorf("unknown measurement key type: %T", t) + if o.Value == nil { + return errors.New("Mkey value not set") + } + + if err := o.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", o.Value.Type(), err) } + return nil } -func (o Mkey) IsPSARefValID() bool { - _, ok := o.val.(TaggedPSARefValID) - return ok -} +// UnmarshalJSON deserializes the supplied JSON object into the target MKey +// The key object must have the following shape: +// +// { +// "type": "", +// "value": +// } +// +// where must be one of the known IMKeyValue implementation +// type names (available in the base implementation: "uuid", "oid", +// "psa.impl-id"), and is the class id value serialized to +// JSON. The exact serialization is depenent. For the base +// implementation types it is +// +// oid: dot-seprated integers, e.g. "1.2.3.4" +// uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" +// psa.refval-id: JSON representation of the PSA refval-id +func (o *Mkey) UnmarshalJSON(data []byte) error { + var tnv encoding.TypeAndValue + + if err := json.Unmarshal(data, &tnv); err != nil { + return err + } + + decoded, err := NewMkey(nil, tnv.Type) + if err != nil { + return err + } + + if err := json.Unmarshal(tnv.Value, decoded.Value); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value -func (o Mkey) IsCCAPlatformConfigID() bool { - _, ok := o.val.(TaggedCCAPlatformConfigID) - return ok + return nil } -func (o Mkey) GetPSARefValID() (PSARefValID, error) { - switch t := o.val.(type) { - case TaggedPSARefValID: - return PSARefValID(t), nil - default: - return PSARefValID{}, fmt.Errorf("measurement-key type is: %T", t) +// MarshalJSON serializes the target Mkey into the type'n'value JSON object +func (o Mkey) MarshalJSON() ([]byte, error) { + valueBytes, err := json.Marshal(o.Value) + if err != nil { + return nil, err + } + + value := encoding.TypeAndValue{ + Type: o.Value.Type(), + Value: valueBytes, } + + return json.Marshal(value) } -func (o Mkey) GetCCAPlatformConfigID() (CCAPlatformConfigID, error) { - switch t := o.val.(type) { - case TaggedCCAPlatformConfigID: - return CCAPlatformConfigID(t), nil - default: - return CCAPlatformConfigID(""), fmt.Errorf("measurement-key type is: %T", t) +// MarshalCBOR serializes the taret mkey into CBOR-encoded bytes. +func (o Mkey) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.Value) +} + +// UnmarshalCBOR deserializes the Mkey from the provided CBOR bytes. +func (o *Mkey) UnmarshalCBOR(data []byte) error { + if len(data) == 0 { + return errors.New("empty input") } + + majorType := (data[0] & 0xe0) >> 5 + if majorType == 6 { // tag + return dm.Unmarshal(data, &o.Value) + } + + // untagged value must be a uint + + var val UintMkey + if err := dm.Unmarshal(data, &val); err != nil { + return err + } + + o.Value = &val + return nil +} + +// IMKeyValue is the interface implemented by all Mkey value implementations. +type IMKeyValue interface { + extensions.ITypeChoiceValue } -func (o Mkey) GetKeyUint() (uint64, error) { - switch t := o.val.(type) { +const UintType = "uint" + +type UintMkey uint64 + +func NewUintMkey(val any) (*UintMkey, error) { + var ret UintMkey + + if val == nil { + return &ret, nil + } + + switch t := val.(type) { + case UintMkey: + ret = t + case *UintMkey: + ret = *t + case string: + u, err := strconv.ParseUint(t, 10, 64) + if err != nil { + return nil, err + } + ret = UintMkey(u) case uint64: - return t, nil + ret = UintMkey(t) + case uint: + ret = UintMkey(t) default: - return MaxUint64, fmt.Errorf("measurement-key type is: %T", t) + return nil, fmt.Errorf("unexpected type for UintMkey: %T", t) } + + return &ret, nil } -// UnmarshalJSON deserializes the type'n'value JSON object into the target Mkey -func (o *Mkey) UnmarshalJSON(data []byte) error { - var v tnv +func (o UintMkey) Valid() error { + return nil +} + +func (o UintMkey) String() string { + return fmt.Sprint(uint64(o)) +} - if err := json.Unmarshal(data, &v); err != nil { +func (o UintMkey) Type() string { + return UintType +} + +func (o *UintMkey) UnmarshalJSON(data []byte) error { + var tmp uint64 + + if err := json.Unmarshal(data, &tmp); err != nil { return err } - switch v.Type { - case "uuid": - var x UUID - if err := x.UnmarshalJSON(v.Value); err != nil { - return fmt.Errorf( - "cannot unmarshal $measured-element-type-choice of type UUID: %w", - err, - ) - } - o.val = TaggedUUID(x) - case "psa.refval-id": - var x PSARefValID - if err := json.Unmarshal(v.Value, &x); err != nil { - return fmt.Errorf( - "cannot unmarshal $measured-element-type-choice of type PSARefValID: %w", - err, - ) - } - if err := x.Valid(); err != nil { - return fmt.Errorf( - "cannot unmarshal $measured-element-type-choice of type PSARefValID: %w", - err, - ) - } - o.val = TaggedPSARefValID(x) - case "cca.platform-config-id": - var x CCAPlatformConfigID - if err := json.Unmarshal(v.Value, &x); err != nil { - return fmt.Errorf( - "cannot unmarshal $measured-element-type-choice of type CCAPlatformConfigID: %w", - err, - ) - } - if x.Empty() { - return fmt.Errorf( - "cannot unmarshal $measured-element-type-choice of type CCAPlatformConfigID: empty label", - ) - } - o.val = TaggedCCAPlatformConfigID(x) - case "uint": - var x uint64 - if err := json.Unmarshal(v.Value, &x); err != nil { - return fmt.Errorf( - "cannot unmarshal $measured-element-type-choice of type uint: %w", - err, - ) - } - o.val = x - default: - return fmt.Errorf("unknown type %s for $measured-element-type-choice", v.Type) - } + *o = UintMkey(tmp) return nil } -// MarshalJSON serializes the target Mkey into the type'n'value JSON object -// Supported types are: uuid, psa.refval-id and unsigned integer -func (o Mkey) MarshalJSON() ([]byte, error) { - var ( - v tnv - b []byte - err error - ) - - switch t := o.val.(type) { - case TaggedUUID: - uuidString := UUID(t).String() - b, err = json.Marshal(uuidString) - if err != nil { - return nil, err - } - v = tnv{Type: "uuid", Value: b} - case TaggedPSARefValID: - b, err = json.Marshal(t) - if err != nil { - return nil, err - } - v = tnv{Type: "psa.refval-id", Value: b} - case TaggedCCAPlatformConfigID: - b, err = json.Marshal(t) - if err != nil { - return nil, err - } - v = tnv{Type: "cca.platform-config-id", Value: b} +func NewMkeyOID(val any) (*Mkey, error) { + ret, err := NewTaggedOID(val) + if err != nil { + return nil, err + } - case uint64: - b, err = json.Marshal(t) - if err != nil { - return nil, err - } - v = tnv{Type: "uint", Value: b} + return &Mkey{ret}, nil +} - default: - return nil, fmt.Errorf("unknown type %T for mkey", t) +func NewMkeyUUID(val any) (*Mkey, error) { + ret, err := NewTaggedUUID(val) + if err != nil { + return nil, err } - return json.Marshal(v) + return &Mkey{ret}, nil } -func (o Mkey) MarshalCBOR() ([]byte, error) { - return em.Marshal(o.val) +func NewMkeyUint(val any) (*Mkey, error) { + ret, err := NewUintMkey(val) + if err != nil { + return nil, err + } + + return &Mkey{ret}, nil } -func (o *Mkey) UnmarshalCBOR(data []byte) error { - return dm.Unmarshal(data, &o.val) +func NewMkeyPSARefvalID(val any) (*Mkey, error) { + ret, err := NewTaggedPSARefValID(val) + if err != nil { + return nil, err + } + + return &Mkey{ret}, nil +} + +func NewMkeyCCAPlatformConfigID(val any) (*Mkey, error) { + ret, err := NewTaggedCCAPlatformConfigID(val) + if err != nil { + return nil, err + } + + return &Mkey{ret}, nil +} + +// IMkeyFactory defines the signature for the factory functions that may be +// registred using RegisterMkeyType to provide a new implementation of the +// corresponding type choice. The factory function should create a new *Mkey +// with the underlying value created based on the provided input. The range of +// valid inputs is up to the specific type choice implementation, however it +// _must_ accept nil as one of the inputs, and return the Zero value for +// implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IMkeyFactory = func(val any) (*Mkey, error) + +var mkeyValueRegister = map[string]IMkeyFactory{ + OIDType: NewMkeyOID, + UUIDType: NewMkeyUUID, + UintType: NewMkeyUint, + PSARefValIDType: NewMkeyPSARefvalID, + CCAPlatformConfigIDType: NewMkeyCCAPlatformConfigID, +} + +// RegisterMkeyType registers a new IMKeyValue implementation +// (created by the provided IMKeyFactory) under the specified CBOR tag. +func RegisterMkeyType(tag uint64, factory IMkeyFactory) error { + + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Value.Type() + if _, exists := mkeyValueRegister[typ]; exists { + return fmt.Errorf("mesurement key type with name %q already exists", typ) + } + + if err := registerCOMIDTag(tag, nilVal.Value); err != nil { + return err + } + + mkeyValueRegister[typ] = factory + + return nil } // Mval stores a measurement-values-map with JSON and CBOR serializations. @@ -330,95 +417,112 @@ func (o Version) Valid() error { return nil } -// NewMeasurement instantiates an empty measurement -func NewMeasurement() *Measurement { - return &Measurement{} +// Measurement stores a measurement-map with CBOR and JSON serializations. +type Measurement struct { + Key *Mkey `cbor:"0,keyasint,omitempty" json:"key,omitempty"` + Val Mval `cbor:"1,keyasint" json:"value"` + AuthorizedBy *CryptoKey `cbor:"2,keyasint,omitempty" json:"authorized-by,omitempty"` } -// SetKeyPSARefValID sets the key of the target measurement-map to the supplied -// PSA refval-id -func (o *Measurement) SetKeyPSARefValID(psaRefValID PSARefValID) *Measurement { - if o != nil { - if psaRefValID.Valid() != nil { - return nil - } - o.Key = &Mkey{ - val: TaggedPSARefValID(psaRefValID), - } +func NewMeasurement(val any, typ string) (*Measurement, error) { + keyFactory, ok := mkeyValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unknown Mkey type: %s", typ) } - return o -} -// SetKeyCCAPlatformConfigID sets the key of the target measurement-map to the supplied -// CCA platform-config-id -func (o *Measurement) SetKeyCCAPlatformConfigID(ccaPlatformConfigID CCAPlatformConfigID) *Measurement { - if o != nil { - if ccaPlatformConfigID.Empty() { - return nil - } - o.Key = &Mkey{ - val: TaggedCCAPlatformConfigID(ccaPlatformConfigID), - } + key, err := keyFactory(val) + if err != nil { + return nil, fmt.Errorf("invalid key: %w", err) } - return o -} -// SetKeyKeyUUID sets the key of the target measurement-map to the supplied -// UUID -func (o *Measurement) SetKeyUUID(u UUID) *Measurement { - if o != nil { - if u.Empty() { - return nil - } + if err = key.Valid(); err != nil { + return nil, fmt.Errorf("invalid key: %w", err) + } - if u.Valid() != nil { - return nil - } + var ret Measurement + ret.Key = key - o.Key = &Mkey{ - val: TaggedUUID(u), - } - } - return o + return &ret, nil } -// SetKeyUint sets the key of the target measurement-map to the supplied -// unsigned integer -func (o *Measurement) SetKeyUint(u uint64) *Measurement { - if o != nil { - o.Key = &Mkey{ - val: u, - } +func MustNewMeasurement(val any, typ string) *Measurement { + ret, err := NewMeasurement(val, typ) + + if err != nil { + panic(err) } - return o + + return ret } // NewPSAMeasurement instantiates a new measurement-map with the key set to the // supplied PSA refval-id -func NewPSAMeasurement(psaRefValID PSARefValID) *Measurement { - m := &Measurement{} - return m.SetKeyPSARefValID(psaRefValID) +func NewPSAMeasurement(key any) (*Measurement, error) { + return NewMeasurement(key, PSARefValIDType) +} + +func MustNewPSAMeasurement(key any) *Measurement { + ret, err := NewPSAMeasurement(key) + + if err != nil { + panic(err) + } + + return ret } // NewCCAPlatCfgMeasurement instantiates a new measurement-map with the key set to the // supplied CCA platform-config-id -func NewCCAPlatCfgMeasurement(ccaPlatformConfigID CCAPlatformConfigID) *Measurement { - m := &Measurement{} - return m.SetKeyCCAPlatformConfigID(ccaPlatformConfigID) +func NewCCAPlatCfgMeasurement(key any) (*Measurement, error) { + return NewMeasurement(key, CCAPlatformConfigIDType) +} + +func MustNewCCAPlatCfgMeasurement(key any) *Measurement { + ret, err := NewCCAPlatCfgMeasurement(key) + + if err != nil { + panic(err) + } + + return ret } // NewUUIDMeasurement instantiates a new measurement-map with the key set to the // supplied UUID -func NewUUIDMeasurement(uuid UUID) *Measurement { - m := &Measurement{} - return m.SetKeyUUID(uuid) +func NewUUIDMeasurement(key any) (*Measurement, error) { + return NewMeasurement(key, UUIDType) +} + +func MustNewUUIDMeasurement(key any) *Measurement { + ret, err := NewUUIDMeasurement(key) + + if err != nil { + panic(err) + } + + return ret } // NewUintMeasurement instantiates a new measurement-map with the key set to the // supplied Uint -func NewUintMeasurement(mkey uint64) *Measurement { - m := &Measurement{} - return m.SetKeyUint(mkey) +func NewUintMeasurement(key any) (*Measurement, error) { + return NewMeasurement(key, UintType) +} + +func MustNewUintMeasurement(key any) *Measurement { + ret, err := NewUintMeasurement(key) + + if err != nil { + panic(err) + } + + return ret +} + +// NewOIDMeasurement instantiates a new measurement-map with the key set to the +// supplied OID +func NewOIDMeasurement(key any) (*Measurement, error) { + return NewMeasurement(key, OIDType) } func (o *Measurement) SetVersion(ver string, scheme int64) *Measurement { @@ -448,26 +552,14 @@ func (o *Measurement) SetRawValueBytes(rawValue, rawValueMask []byte) *Measureme // SetSVN sets the supplied svn in the measurement-values-map of the target // measurement func (o *Measurement) SetSVN(svn uint64) *Measurement { - if o != nil { - s := SVN{} - if s.SetSVN(svn) == nil { - return nil - } - o.Val.SVN = &s - } + o.Val.SVN = MustNewTaggedSVN(svn) return o } // SetMinSVN sets the supplied min-svn in the measurement-values-map of the // target measurement func (o *Measurement) SetMinSVN(svn uint64) *Measurement { - if o != nil { - s := SVN{} - if s.SetMinSVN(svn) == nil { - return nil - } - o.Val.SVN = &s - } + o.Val.SVN = MustNewTaggedMinSVN(svn) return o } diff --git a/comid/measurement_test.go b/comid/measurement_test.go index d5d58bff..c4f95e11 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -4,6 +4,7 @@ package comid import ( + "crypto" "fmt" "testing" @@ -14,86 +15,86 @@ import ( ) func TestMeasurement_NewUUIDMeasurement_good_uuid(t *testing.T) { - tv := NewUUIDMeasurement(TestUUID) - assert.NotNil(t, tv) + _, err := NewUUIDMeasurement(TestUUID) + assert.NoError(t, err) } func TestMeasurement_NewUUIDMeasurement_empty_uuid(t *testing.T) { emptyUUID := UUID{} - tv := NewUUIDMeasurement(emptyUUID) + _, err := NewUUIDMeasurement(emptyUUID) - assert.Nil(t, tv) + assert.EqualError(t, err, + "invalid key: expecting RFC4122 UUID, got Reserved instead") } func TestMeasurement_NewUIntMeasurement(t *testing.T) { var TestUint uint64 = 35 - tv := NewUintMeasurement(TestUint) + _, err := NewUintMeasurement(TestUint) - assert.NotNil(t, tv) + assert.NoError(t, err) } func TestMeasurement_NewPSAMeasurement_empty(t *testing.T) { emptyPSARefValID := PSARefValID{} - tv := NewPSAMeasurement(emptyPSARefValID) - assert.Nil(t, tv) + _, err := NewPSAMeasurement(emptyPSARefValID) + + assert.EqualError(t, err, "invalid key: invalid psa.refval-id: missing mandatory signer ID") } func TestMeasurement_NewPSAMeasurement_no_values(t *testing.T) { - psaRefValID := - NewPSARefValID(TestSignerID). - SetLabel("PRoT"). - SetVersion("1.2.3") + psaRefValID, err := NewPSARefValID(TestSignerID) + require.NoError(t, err) + psaRefValID.SetLabel("PRoT") + psaRefValID.SetVersion("1.2.3") require.NotNil(t, psaRefValID) - tv := NewPSAMeasurement(*psaRefValID) - assert.NotNil(t, tv) + tv, err := NewPSAMeasurement(*psaRefValID) + assert.NoError(t, err) - err := tv.Valid() + err = tv.Valid() assert.EqualError(t, err, "no measurement value set") } func TestMeasurement_NewCCAPlatCfgMeasurement_no_values(t *testing.T) { ccaplatID := CCAPlatformConfigID(TestCCALabel) - tv := NewCCAPlatCfgMeasurement(ccaplatID) - assert.NotNil(t, tv) + tv, err := NewCCAPlatCfgMeasurement(ccaplatID) + assert.NoError(t, err) - err := tv.Valid() + err = tv.Valid() assert.EqualError(t, err, "no measurement value set") } func TestMeasurement_NewCCAPlatCfgMeasurement_valid_meas(t *testing.T) { ccaplatID := CCAPlatformConfigID(TestCCALabel) - tv := NewCCAPlatCfgMeasurement(ccaplatID).SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{}) - assert.NotNil(t, tv) + tv, err := NewCCAPlatCfgMeasurement(ccaplatID) + assert.NoError(t, err) - err := tv.Valid() - assert.Nil(t, err) + tv.SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{}) + + err = tv.Valid() + assert.NoError(t, err) } func TestMeasurement_NewPSAMeasurement_one_value(t *testing.T) { - psaRefValID := - NewPSARefValID(TestSignerID). - SetLabel("PRoT"). - SetVersion("1.2.3") - require.NotNil(t, psaRefValID) + tv, err := NewPSAMeasurement(MustCreatePSARefValID(TestSignerID, "PRoT", "1.2.3")) + require.NoError(t, err) - tv := NewPSAMeasurement(*psaRefValID).SetIPaddr(TestIPaddr) - assert.NotNil(t, tv) + tv.SetIPaddr(TestIPaddr) - err := tv.Valid() + err = tv.Valid() assert.Nil(t, err) } func TestMeasurement_NewUUIDMeasurement_no_values(t *testing.T) { - tv := NewUUIDMeasurement(TestUUID) - require.NotNil(t, tv) + tv, err := NewUUIDMeasurement(TestUUID) + require.NoError(t, err) - err := tv.Valid() + err = tv.Valid() assert.EqualError(t, err, "no measurement value set") } @@ -101,26 +102,27 @@ func TestMeasurement_NewUUIDMeasurement_some_value(t *testing.T) { var vs swid.VersionScheme require.NoError(t, vs.SetCode(swid.VersionSchemeSemVer)) - tv := NewUUIDMeasurement(TestUUID). - SetMinSVN(2). + tv, err := NewUUIDMeasurement(TestUUID) + require.NoError(t, err) + + tv.SetMinSVN(2). SetFlagsTrue(FlagIsDebug). SetVersion("1.2.3", swid.VersionSchemeSemVer) - require.NotNil(t, tv) - err := tv.Valid() + err = tv.Valid() assert.Nil(t, err) } func TestMeasurement_NewUUIDMeasurement_bad_digest(t *testing.T) { - tv := NewUUIDMeasurement(TestUUID) - require.NotNil(t, tv) + tv, err := NewUUIDMeasurement(TestUUID) + require.NoError(t, err) assert.Nil(t, tv.AddDigest(swid.Sha256, []byte{0xff})) } func TestMeasurement_NewUUIDMeasurement_bad_ueid(t *testing.T) { - tv := NewUUIDMeasurement(TestUUID) - require.NotNil(t, tv) + tv, err := NewUUIDMeasurement(TestUUID) + require.NoError(t, err) badUEID := eat.UEID{ 0xFF, // Invalid @@ -131,8 +133,8 @@ func TestMeasurement_NewUUIDMeasurement_bad_ueid(t *testing.T) { } func TestMeasurement_NewUUIDMeasurement_bad_uuid(t *testing.T) { - tv := NewUUIDMeasurement(TestUUID) - require.NotNil(t, tv) + tv, err := NewUUIDMeasurement(TestUUID) + require.NoError(t, err) nonRFC4122UUID, err := ParseUUID("f47ac10b-58cc-4372-c567-0e02b2c3d479") require.Nil(t, err) @@ -147,7 +149,7 @@ var ( func TestMkey_Valid_no_value(t *testing.T) { mkey := &Mkey{} - expectedErr := "unknown measurement key type: " + expectedErr := "Mkey value not set" err := mkey.Valid() assert.EqualError(t, err, expectedErr) } @@ -183,19 +185,19 @@ func TestMKey_MarshalCBOR_CCAPlatformConfigID_ok(t *testing.T) { func TestMKey_UnmarshalCBOR_CCAPlatformConfigID_ok(t *testing.T) { tvs := []struct { input []byte - expected CCAPlatformConfigID + expected TaggedCCAPlatformConfigID }{ { input: MustHexDecode(t, "d9025a736363612d706c6174666f726d2d636f6e666967"), - expected: CCAPlatformConfigID(TestCCALabel), + expected: TaggedCCAPlatformConfigID(TestCCALabel), }, { input: MustHexDecode(t, "d9025a716d7974657374706c6174666f726d666967"), - expected: CCAPlatformConfigID("mytestplatformfig"), + expected: TaggedCCAPlatformConfigID("mytestplatformfig"), }, { input: MustHexDecode(t, "d9025a6c6d79746573746c6162656c32"), - expected: CCAPlatformConfigID("mytestlabel2"), + expected: TaggedCCAPlatformConfigID("mytestlabel2"), }, } @@ -203,9 +205,9 @@ func TestMKey_UnmarshalCBOR_CCAPlatformConfigID_ok(t *testing.T) { mkey := &Mkey{} err := mkey.UnmarshalCBOR(tv.input) assert.Nil(t, err) - actual, err := mkey.GetCCAPlatformConfigID() - assert.Nil(t, err) - assert.Equal(t, tv.expected, actual) + actual, ok := mkey.Value.(*TaggedCCAPlatformConfigID) + assert.True(t, ok) + assert.Equal(t, tv.expected, *actual) fmt.Printf("CBOR: %x\n", actual) } } @@ -230,36 +232,13 @@ func TestMKey_MarshalCBOR_uint_ok(t *testing.T) { } for _, tv := range tvs { - mkey := &Mkey{tv.mkey} + mkey := &Mkey{UintMkey(tv.mkey)} actual, err := mkey.MarshalCBOR() assert.Nil(t, err) assert.Equal(t, tv.expected, actual) fmt.Printf("CBOR: %x\n", actual) } } -func TestMkey_MarshalCBOR_uint_not_ok(t *testing.T) { - tvs := []struct { - input interface{} - expected string - }{ - { - input: 123.456, - expected: "unknown measurement key type: float64", - }, - { - input: "sample", - expected: "unknown measurement key type: string", - }, - } - - for _, tv := range tvs { - mkey := &Mkey{tv.input} - _, err := mkey.MarshalCBOR() - assert.Nil(t, err) - err = mkey.Valid() - assert.EqualError(t, err, tv.expected) - } -} func TestMkey_UnmarshalCBOR_uint_ok(t *testing.T) { tvs := []struct { @@ -284,10 +263,10 @@ func TestMkey_UnmarshalCBOR_uint_ok(t *testing.T) { mKey := &Mkey{} err := mKey.UnmarshalCBOR(tv.mkey) - assert.Nil(t, err) - actual, err := mKey.GetKeyUint() - assert.Nil(t, err) - assert.Equal(t, tv.expected, actual) + require.NoError(t, err) + actual, ok := mKey.Value.(*UintMkey) + require.True(t, ok) + assert.Equal(t, tv.expected, uint64(*actual)) } } @@ -315,38 +294,9 @@ func TestMkey_UnmarshalCBOR_not_ok(t *testing.T) { } } -func TestMkey_UnmarshalCBOR_uint_not_ok(t *testing.T) { - tvs := []struct { - input []byte - expected string - }{ - { - input: []byte{0xd8, 0x25, 0x50, 0x31, 0xfb, 0x5a, 0xbf, 0x02, - 0x3e, 0x49, 0x92, 0xaa, 0x4e, 0x95, 0xf9, 0xc1, - 0x50, 0x3b, 0xfa}, - expected: "measurement-key type is: comid.TaggedUUID", - }, - { - input: []byte{0xd8, 0x21, 0x50, 0x31, 0xfb, 0x5a, 0xff, 0x12, - 0xFF, 0xFF, 0x92, 0xaa, 0x4e, 0x95, 0xf9, 0xc1, - 0x50, 0x3b, 0xfa}, - expected: "measurement-key type is: cbor.Tag", - }, - } - - for _, tv := range tvs { - mKey := &Mkey{} - - err := mKey.UnmarshalCBOR(tv.input) - assert.Nil(t, err) - _, err = mKey.GetKeyUint() - assert.EqualError(t, err, tv.expected) - } -} - func TestMKey_MarshalJSON_CCAPlatformConfigID_ok(t *testing.T) { refval := TestCCALabel - mkey := &Mkey{val: TaggedCCAPlatformConfigID(refval)} + mkey := &Mkey{Value: TaggedCCAPlatformConfigID(refval)} expected := `{"type":"cca.platform-config-id","value":"cca-platform-config"}` @@ -359,30 +309,29 @@ func TestMKey_MarshalJSON_CCAPlatformConfigID_ok(t *testing.T) { func TestMKey_UnMarshalJSON_CCAPlatformConfigID_ok(t *testing.T) { input := []byte(`{"type":"cca.platform-config-id","value":"cca-platform-config"}`) - expected := CCAPlatformConfigID(TestCCALabel) + expected := TaggedCCAPlatformConfigID(TestCCALabel) mKey := &Mkey{} err := mKey.UnmarshalJSON(input) assert.Nil(t, err) - actual, err := mKey.GetCCAPlatformConfigID() - assert.Nil(t, err) - assert.Equal(t, expected, actual) + actual, ok := mKey.Value.(*TaggedCCAPlatformConfigID) + assert.True(t, ok) + assert.Equal(t, expected, *actual) } func TestMKey_UnMarshalJSON_CCAPlatformConfigID_not_ok(t *testing.T) { input := []byte(`{"type":"cca.platform-config-id","value":""}`) - expected := "cannot unmarshal $measured-element-type-choice of type CCAPlatformConfigID: empty label" + expected := "invalid cca.platform-config-id: empty value" mKey := &Mkey{} err := mKey.UnmarshalJSON(input) - assert.NotNil(t, err) - assert.Equal(t, expected, err.Error()) - + assert.EqualError(t, err, expected) } + func TestMkey_MarshalJSON_uint_ok(t *testing.T) { tvs := []struct { mkey uint64 @@ -404,7 +353,7 @@ func TestMkey_MarshalJSON_uint_ok(t *testing.T) { for _, tv := range tvs { - mkey := &Mkey{tv.mkey} + mkey := &Mkey{UintMkey(tv.mkey)} actual, err := mkey.MarshalJSON() assert.Nil(t, err) @@ -414,31 +363,6 @@ func TestMkey_MarshalJSON_uint_ok(t *testing.T) { } } -func TestMkey_MarshalJSON_uint_not_ok(t *testing.T) { - tvs := []struct { - input interface{} - expected string - }{ - { - input: 123.456, - expected: "unknown type float64 for mkey", - }, - { - input: "sample", - expected: "unknown type string for mkey", - }, - } - - for _, tv := range tvs { - - mkey := &Mkey{tv.input} - - _, err := mkey.MarshalJSON() - - assert.EqualError(t, err, tv.expected) - } -} - func TestMkey_UnmarshalJSON_uint_ok(t *testing.T) { tvs := []struct { input []byte @@ -463,9 +387,9 @@ func TestMkey_UnmarshalJSON_uint_ok(t *testing.T) { err := mKey.UnmarshalJSON(tv.input) assert.Nil(t, err) - actual, err := mKey.GetKeyUint() - assert.Nil(t, err) - assert.Equal(t, tv.expected, actual) + actual, ok := mKey.Value.(*UintMkey) + assert.True(t, ok) + assert.Equal(t, tv.expected, uint64(*actual)) } } @@ -476,11 +400,11 @@ func TestMkey_UnmarshalJSON_notok(t *testing.T) { }{ { input: []byte(`{"type":"uint","value":"abcdefg"}`), - expected: "cannot unmarshal $measured-element-type-choice of type uint: json: cannot unmarshal string into Go value of type uint64", + expected: `invalid uint: json: cannot unmarshal string into Go value of type uint64`, }, { input: []byte(`{"type":"uint","value":123.456}`), - expected: "cannot unmarshal $measured-element-type-choice of type uint: json: cannot unmarshal number 123.456 into Go value of type uint64", + expected: "invalid uint: json: cannot unmarshal number 123.456 into Go value of type uint64", }, } @@ -493,27 +417,102 @@ func TestMkey_UnmarshalJSON_notok(t *testing.T) { } } -func TestMkey_UnmarshalJSON_uint_notok(t *testing.T) { +func TestNewUintMkey(t *testing.T) { + testVal := UintMkey(7) + tvs := []struct { - input []byte - expected string + input any + expected UintMkey + err string }{ { - input: []byte(`{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}`), - expected: "measurement-key type is: comid.TaggedUUID", + input: testVal, + expected: testVal, + }, + { + input: &testVal, + expected: testVal, }, { - input: []byte(`{"type":"psa.refval-id","value":{"label": "BL","version": "2.1.0","signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}}`), - expected: "measurement-key type is: comid.TaggedPSARefValID", + input: uint(7), + expected: testVal, + }, + { + input: uint64(7), + expected: testVal, + }, + { + input: "7", + expected: testVal, + }, + { + input: true, + err: "unexpected type for UintMkey: bool", }, } for _, tv := range tvs { - mKey := &Mkey{} - - err := mKey.UnmarshalJSON(tv.input) - assert.Nil(t, err) - _, err = mKey.GetKeyUint() - assert.EqualError(t, err, tv.expected) + out, err := NewUintMkey(tv.input) + if tv.err != "" { + assert.Nil(t, out) + assert.EqualError(t, err, tv.err) + } else { + assert.Equal(t, tv.expected, *out) + } } } + +func TestNewMkeyOID(t *testing.T) { + var expectedOID OID + require.NoError(t, expectedOID.FromString(TestOID)) + expected := TaggedOID(expectedOID) + + out, err := NewMkeyOID(TestOID) + require.NoError(t, err) + assert.Equal(t, &expected, out.Value) +} + +type testMkey [4]byte + +func newTestMkey(val any) (*Mkey, error) { + return &Mkey{&testMkey{0x74, 0x64, 0x73, 0x74}}, nil +} + +func (o testMkey) PublicKey() (crypto.PublicKey, error) { + return crypto.PublicKey(o[:]), nil +} + +func (o testMkey) Type() string { + return "test-mkey" +} + +func (o testMkey) String() string { + return "test" +} + +func (o testMkey) Valid() error { + return nil +} + +type badMkey struct { + testMkey +} + +func (o badMkey) Type() string { + return "uuid" +} + +func newBadMkey(val any) (*Mkey, error) { + return &Mkey{&badMkey{testMkey{0x74, 0x64, 0x73, 0x74}}}, nil +} + +func TestRegisterMkeyType(t *testing.T) { + err := RegisterMkeyType(32, newTestMkey) + assert.EqualError(t, err, "tag 32 is already registered") + + err = RegisterMkeyType(99996, newBadMkey) + assert.EqualError(t, err, `mesurement key type with name "uuid" already exists`) + + err = RegisterMkeyType(99996, newTestMkey) + assert.NoError(t, err) +} diff --git a/comid/oid.go b/comid/oid.go index 822d7deb..24d366e6 100644 --- a/comid/oid.go +++ b/comid/oid.go @@ -11,6 +11,8 @@ import ( "strings" ) +const OIDType = "oid" + // BER-encoded absolute OID type OID []byte @@ -152,3 +154,78 @@ func (o *OID) UnmarshalJSON(data []byte) error { func (o OID) MarshalJSON() ([]byte, error) { return json.Marshal(o.String()) } + +type TaggedOID OID + +func NewTaggedOID(val any) (*TaggedOID, error) { + ret := TaggedOID{} + + if val == nil { + return &ret, nil + } + + switch t := val.(type) { + case string: + var berOID OID + if err := berOID.FromString(t); err != nil { + return nil, err + } + + ret = TaggedOID(berOID) + case []byte: + ret = make([]byte, len(t)) + copy(ret, t) + case TaggedOID: + ret = make([]byte, len(t)) + copy(ret, t) + case OID: + ret = make([]byte, len(t)) + copy(ret, t) + case *TaggedOID: + ret = make([]byte, len(*t)) + copy(ret, (*t)) + case *OID: + ret = make([]byte, len(*t)) + copy(ret, (*t)) + } + + return &ret, nil +} + +func (o TaggedOID) Type() string { + return OIDType +} + +func (o TaggedOID) String() string { + return OID(o).String() +} + +func (o TaggedOID) Valid() error { + return nil +} + +func (o TaggedOID) Bytes() []byte { + return o +} + +func (o *TaggedOID) FromString(s string) error { + return (*OID)(o).FromString(s) +} + +func (o *TaggedOID) UnmarshalJSON(data []byte) error { + var s string + + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + if err := o.FromString(s); err != nil { + return err + } + + return nil +} + +func (o TaggedOID) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} diff --git a/comid/psareferencevalue.go b/comid/psareferencevalue.go index cde3304b..0ffacf97 100644 --- a/comid/psareferencevalue.go +++ b/comid/psareferencevalue.go @@ -4,9 +4,12 @@ package comid import ( + "encoding/json" "fmt" ) +var PSARefValIDType = "psa.refval-id" + // PSARefValID stores a PSA refval-id with CBOR and JSON serializations // (See https://datatracker.ietf.org/doc/html/draft-xyz-rats-psa-endorsements) type PSARefValID struct { @@ -30,18 +33,56 @@ func (o PSARefValID) Valid() error { return nil } -type TaggedPSARefValID PSARefValID +func CreatePSARefValID(signerID []byte, label, version string) (*PSARefValID, error) { + ret, err := NewPSARefValID(signerID) + if err != nil { + return nil, err + } -func NewPSARefValID(signerID []byte) *PSARefValID { - switch len(signerID) { - case 32, 48, 64: - default: - return nil + ret.SetLabel(label) + ret.SetVersion(version) + + return ret, nil +} + +func MustCreatePSARefValID(signerID []byte, label, version string) *PSARefValID { + ret, err := CreatePSARefValID(signerID, label, version) + + if err != nil { + panic(err) } - return &PSARefValID{ - SignerID: signerID, + return ret +} + +func NewPSARefValID(val any) (*PSARefValID, error) { + var ret PSARefValID + + if val == nil { + return &ret, nil } + + switch t := val.(type) { + case PSARefValID: + ret = t + case *PSARefValID: + ret = *t + case string: + if err := json.Unmarshal([]byte(t), &ret); err != nil { + return nil, err + } + case []byte: + switch len(t) { + case 32, 48, 64: + ret.SignerID = t + default: + return nil, fmt.Errorf("invalid PSA RefVal ID length: %d", len(t)) + } + default: + return nil, fmt.Errorf("unexpected type for PSA RefVal ID: %T", t) + } + + return &ret, nil } func (o *PSARefValID) SetLabel(label string) *PSARefValID { @@ -57,3 +98,46 @@ func (o *PSARefValID) SetVersion(version string) *PSARefValID { } return o } + +type TaggedPSARefValID PSARefValID + +func NewTaggedPSARefValID(val any) (*TaggedPSARefValID, error) { + var ret TaggedPSARefValID + + switch t := val.(type) { + case TaggedPSARefValID: + ret = t + case *TaggedPSARefValID: + ret = *t + default: + refvalID, err := NewPSARefValID(val) + if err != nil { + return nil, err + } + ret = TaggedPSARefValID(*refvalID) + + } + + return &ret, nil +} + +func (o TaggedPSARefValID) Valid() error { + return PSARefValID(o).Valid() +} + +func (o TaggedPSARefValID) String() string { + ret, err := json.Marshal(o) + if err != nil { + return "" + } + + return string(ret) +} + +func (o TaggedPSARefValID) Type() string { + return PSARefValIDType +} + +func (o TaggedPSARefValID) IsZero() bool { + return len(o.SignerID) == 0 +} diff --git a/comid/psareferencevalue_test.go b/comid/psareferencevalue_test.go index 069c432e..72116cc2 100644 --- a/comid/psareferencevalue_test.go +++ b/comid/psareferencevalue_test.go @@ -4,9 +4,11 @@ package comid import ( + "fmt" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPSARefValID_Valid_SignerID_range(t *testing.T) { @@ -15,13 +17,27 @@ func TestPSARefValID_Valid_SignerID_range(t *testing.T) { for i := 1; i <= 100; i++ { signerID = append(signerID, byte(0xff)) - tv := NewPSARefValID(signerID) + tv, err := NewPSARefValID(signerID) + switch i { case 32, 48, 64: assert.NotNil(t, tv) assert.Nil(t, tv.Valid()) default: assert.Nil(t, tv) + assert.EqualError( + t, + err, + fmt.Sprintf("invalid PSA RefVal ID length: %d", i), + ) } } } + +func TestPSARefValID_Streing(t *testing.T) { + signerID := MustHexDecode(t, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + refvalID, err := NewTaggedPSARefValID(signerID) + require.NoError(t, err) + + assert.Equal(t, `{"signer-id":"3q2+796tvu/erb7v3q2+796tvu/erb7v3q2+796tvu8="}`, refvalID.String()) +} diff --git a/comid/referencevalue_test.go b/comid/referencevalue_test.go index 00b89d18..f999c876 100644 --- a/comid/referencevalue_test.go +++ b/comid/referencevalue_test.go @@ -13,10 +13,9 @@ func Test_ReferenceValue(t *testing.T) { err := rv.Valid() assert.EqualError(t, err, "environment validation failed: environment must not be empty") - rv.Environment.Instance = NewInstance() id, err := uuid.NewUUID() require.NoError(t, err) - rv.Environment.Instance.SetUUID(id) + rv.Environment.Instance = MustNewUUIDInstance(id) err = rv.Valid() assert.EqualError(t, err, "measurements validation failed: no measurement entries") } diff --git a/comid/rel.go b/comid/rel.go index ad35ed36..39e40d64 100644 --- a/comid/rel.go +++ b/comid/rel.go @@ -18,6 +18,37 @@ const ( RelUnset = ^Rel(0) ) +var ( + relToString = map[Rel]string{ + RelReplaces: "replaces", + RelSupplements: "supplements", + } + + stringToRel = map[string]Rel{ + "replaces": RelReplaces, + "supplements": RelSupplements, + } +) + +// RegisterRel creates a new Rel association between the provided value and +// name. An error is returned if either clashes with any of the existing roles. +func RegisterRel(val int64, name string) error { + rel := Rel(val) + + if _, ok := relToString[rel]; ok { + return fmt.Errorf("rel with value %d already exists", val) + } + + if _, ok := stringToRel[name]; ok { + return fmt.Errorf("rel with name %q already exists", name) + } + + relToString[rel] = name + stringToRel[name] = rel + + return nil +} + func NewRel() *Rel { r := RelUnset return &r @@ -43,14 +74,12 @@ func (o Rel) Valid() error { } func (o Rel) String() string { - switch o { - case RelReplaces: - return "replaces" - case RelSupplements: - return "supplements" - default: - return fmt.Sprintf("rel(%d)", o) + ret, ok := relToString[o] + if ok { + return ret } + + return fmt.Sprintf("rel(%d)", o) } func (o Rel) ToCBOR() ([]byte, error) { diff --git a/comid/rel_test.go b/comid/rel_test.go index 29de1629..86f6d748 100644 --- a/comid/rel_test.go +++ b/comid/rel_test.go @@ -163,3 +163,20 @@ func TestRel_ToCBOR_fail_unset(t *testing.T) { assert.EqualError(t, err, "rel is unset") } + +func Test_RegisterRel(t *testing.T) { + err := RegisterRel(1, "augments") + assert.EqualError(t, err, "rel with value 1 already exists") + + err = RegisterRel(3, "replaces") + assert.EqualError(t, err, `rel with name "replaces" already exists`) + + err = RegisterRel(3, "augments") + assert.NoError(t, err) + + rel := Rel(3) + + out, err := rel.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, `"augments"`, string(out)) +} diff --git a/comid/role.go b/comid/role.go index 1b08322b..3cd3d0a4 100644 --- a/comid/role.go +++ b/comid/role.go @@ -40,6 +40,35 @@ var ( } ) +// String returns the string representation of the Role. +func (o Role) String() string { + text, ok := roleToString[o] + if ok { + return text + } + + return fmt.Sprintf("Role(%d)", o) +} + +// RegisterRole creates a new Role association between the provided value and +// name. An error is returned if either clashes with any of the existing roles. +func RegisterRole(val int64, name string) error { + role := Role(val) + + if _, ok := roleToString[role]; ok { + return fmt.Errorf("role with value %d already exists", val) + } + + if _, ok := stringToRole[name]; ok { + return fmt.Errorf("role with name %q already exists", name) + } + + roleToString[role] = name + stringToRole[name] = role + + return nil +} + type Roles []Role func NewRoles() *Roles { diff --git a/comid/role_test.go b/comid/role_test.go index 4479960c..103f4085 100644 --- a/comid/role_test.go +++ b/comid/role_test.go @@ -210,3 +210,25 @@ func TestRoles_UnmarshalJSON_fail(t *testing.T) { assert.EqualError(t, err, tv.expectedErr) } } + +func Test_Role_String(t *testing.T) { + assert.Equal(t, "maintainer", RoleMaintainer.String()) + assert.Equal(t, "Role(9999)", Role(9999).String()) +} + +func Test_RegisterRole(t *testing.T) { + err := RegisterRole(1, "owner") + assert.EqualError(t, err, "role with value 1 already exists") + + err = RegisterRole(3, "maintainer") + assert.EqualError(t, err, `role with name "maintainer" already exists`) + + err = RegisterRole(3, "owner") + assert.NoError(t, err) + + roles := NewRoles().Add(Role(3)) + + out, err := roles.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, `["owner"]`, string(out)) +} diff --git a/comid/svn.go b/comid/svn.go index ed7a26bb..29fea097 100644 --- a/comid/svn.go +++ b/comid/svn.go @@ -6,102 +6,262 @@ package comid import ( "encoding/json" "fmt" -) + "strconv" -type TaggedSVN uint64 -type TaggedMinSVN uint64 + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" +) +// SVN is the Security Version Number. This typically changes only when a +// security relevant change is needed to the measured environment. type SVN struct { - val interface{} + Value ISVNValue } -func (o *SVN) SetSVN(val uint64) *SVN { - if o != nil { - o.val = TaggedSVN(val) +// NewSVN creates a new SVN of the specified and value. The type must be one of +// the strings defined by the spec ("exact-value", "min-value"), or has been +// registered with RegisterSVNType(). +func NewSVN(val any, typ string) (*SVN, error) { + factory, ok := svnValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unknown SVN type: %s", typ) } - return o + + return factory(val) } -func (o *SVN) SetMinSVN(val uint64) *SVN { - if o != nil { - o.val = TaggedMinSVN(val) +// MustNewSVN is like NewSVN but does not return an error, assuming that the +// provided value is valid. It panics if this is not the case. +func MustNewSVN(val any, typ string) *SVN { + ret, err := NewSVN(val, typ) + if err != nil { + panic(err) } - return o + + return ret } +// MarshalCBOR returns the CBOR encoding of the SVN. func (o SVN) MarshalCBOR() ([]byte, error) { - return em.Marshal(o.val) + return em.Marshal(o.Value) } +// UnmarshalCBOR populates the SVN form the provided CBOR bytes. func (o *SVN) UnmarshalCBOR(data []byte) error { - var svn TaggedSVN + return dm.Unmarshal(data, &o.Value) +} + +// UnmarshalJSON deserializes the supplied JSON object into the target SVN +// The SVN object must have the following shape: +// +// { +// "type": "", +// "value": +// } +// +// where must be one of the known ISVNValue implementation +// type names (available in the base implementation: "exact-value", +// "min-value"), and is the JSON encoding of the underlying +// class id value. The exact encoding is dependent. For both base +// types, it is an integer (JSON number). +func (o *SVN) UnmarshalJSON(data []byte) error { + var tnv encoding.TypeAndValue - if dm.Unmarshal(data, &svn) == nil { - o.val = svn - return nil + if err := json.Unmarshal(data, &tnv); err != nil { + return fmt.Errorf("SVN decoding failure: %w", err) } - var minsvn TaggedMinSVN + decoded, err := NewSVN(nil, tnv.Type) + if err != nil { + return err + } - if dm.Unmarshal(data, &minsvn) == nil { - o.val = svn - return nil + if err := json.Unmarshal(tnv.Value, &decoded.Value); err != nil { + return fmt.Errorf("invalid SVN %s: %w", tnv.Type, err) } - return fmt.Errorf("unknown SVN (CBOR: %x)", data) + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid SVN %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value + + return nil +} + +// MarshalJSON serializes the SVN int a JSON object +func (o SVN) MarshalJSON() ([]byte, error) { + return extensions.TypeChoiceValueMarshalJSON(o.Value) } -type svnJSONRepr tnv +// ISVNValue is the interface that must be implemented by all SVN values. +type ISVNValue interface { + extensions.ITypeChoiceValue +} -// Supported formats: -// { "type": "exact-value", "value": 123 } -> SVN -// { "type": "min-value", "value": 123 } -> MinSVN -func (o *SVN) UnmarshalJSON(data []byte) error { - var s svnJSONRepr +const ( + ExactValueType = "exact-value" + MinValueType = "min-value" +) - if err := json.Unmarshal(data, &s); err != nil { - return fmt.Errorf("SVN decoding failure: %w", err) - } +type TaggedSVN uint64 + +func NewTaggedSVN(val any) (*SVN, error) { + var ret TaggedSVN - var x uint64 - if err := json.Unmarshal(s.Value, &x); err != nil { - return fmt.Errorf( - "cannot unmarshal svn or min-svn: %w", - err, - ) + if val == nil { + return &SVN{&ret}, nil } - switch s.Type { - case "exact-value": - o.val = TaggedSVN(x) - case "min-value": - o.val = TaggedMinSVN(x) + switch t := val.(type) { + case string: + u, err := strconv.ParseUint(t, 10, 64) + if err != nil { + return nil, err + } + ret = TaggedSVN(u) + case TaggedSVN: + ret = t + case *TaggedSVN: + ret = *t + case uint64: + ret = TaggedSVN(t) + case uint: + ret = TaggedSVN(t) + case int: + if t < 0 { + return nil, fmt.Errorf("SVN cannot be negative: %d", t) + } + ret = TaggedSVN(t) + case int64: + if t < 0 { + return nil, fmt.Errorf("SVN cannot be negative: %d", t) + } + ret = TaggedSVN(t) default: - return fmt.Errorf("unknown comparison operator %s", s.Type) + return nil, fmt.Errorf("unexpected type for SVN exact-value: %T", t) } + return &SVN{&ret}, nil +} + +func MustNewTaggedSVN(val any) *SVN { + ret, err := NewTaggedSVN(val) + if err != nil { + panic(err) + } + + return ret +} + +func (o TaggedSVN) String() string { + return fmt.Sprint(uint64(o)) +} + +func (o TaggedSVN) Type() string { + return ExactValueType +} + +func (o TaggedSVN) Valid() error { return nil } -func (o SVN) MarshalJSON() ([]byte, error) { - var ( - v svnJSONRepr - b []byte - err error - ) +type TaggedMinSVN uint64 - b, err = json.Marshal(o.val) - if err != nil { - return nil, err +func NewTaggedMinSVN(val any) (*SVN, error) { + var ret TaggedMinSVN + + if val == nil { + return &SVN{&ret}, nil } - switch t := o.val.(type) { - case TaggedSVN: - v = svnJSONRepr{Type: "exact-value", Value: b} + + switch t := val.(type) { + case string: + u, err := strconv.ParseUint(t, 10, 64) + if err != nil { + return nil, err + } + ret = TaggedMinSVN(u) case TaggedMinSVN: - v = svnJSONRepr{Type: "min-value", Value: b} + ret = t + case *TaggedMinSVN: + ret = *t + case uint64: + ret = TaggedMinSVN(t) + case uint: + ret = TaggedMinSVN(t) + case int: + if t < 0 { + return nil, fmt.Errorf("SVN cannot be negative: %d", t) + } + ret = TaggedMinSVN(t) + case int64: + if t < 0 { + return nil, fmt.Errorf("SVN cannot be negative: %d", t) + } + ret = TaggedMinSVN(t) default: - return nil, fmt.Errorf("unknown SVN type: %T", t) + return nil, fmt.Errorf("unexpected type for SVN min-value: %T", t) + } + + return &SVN{&ret}, nil +} + +func MustNewTaggedMinSVN(val any) *SVN { + ret, err := NewTaggedMinSVN(val) + if err != nil { + panic(err) + } + + return ret +} + +func (o TaggedMinSVN) String() string { + return fmt.Sprint(uint64(o)) +} + +func (o TaggedMinSVN) Type() string { + return MinValueType +} + +func (o TaggedMinSVN) Valid() error { + return nil +} + +// ISVNFactory defines the signature for the factory functions that may be +// registred using RegisterSVNType to provide a new implementation of the +// corresponding type choice. The factory function should create a new *SVN +// with the underlying value created based on the provided input. The range of +// valid inputs is up to the specific type choice implementation, however it +// _must_ accept nil as one of the inputs, and return the Zero value for +// implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type ISVNFactory func(any) (*SVN, error) + +var svnValueRegister = map[string]ISVNFactory{ + ExactValueType: NewTaggedSVN, + MinValueType: NewTaggedMinSVN, +} + +// RegisterSVNType registers a new ISVNValue implementation +// (created by the provided ISVNFactory) under the specified CBOR tag. +func RegisterSVNType(tag uint64, factory ISVNFactory) error { + + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Value.Type() + if _, exists := svnValueRegister[typ]; exists { + return fmt.Errorf("SVN type with name %q already exists", typ) } - return json.Marshal(v) + if err := registerCOMIDTag(tag, nilVal.Value); err != nil { + return err + } + + svnValueRegister[typ] = factory + + return nil } diff --git a/comid/svn_test.go b/comid/svn_test.go new file mode 100644 index 00000000..0ffb8f28 --- /dev/null +++ b/comid/svn_test.go @@ -0,0 +1,182 @@ +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_NewSVN(t *testing.T) { + for _, tv := range []struct { + Name string + Input any + Expected uint64 + Err string + }{ + { + Name: "string ok", + Input: "7", + Expected: 7, + Err: "", + }, + { + Name: "string err", + Input: "test", + Expected: 0, + Err: `strconv.ParseUint: parsing "test": invalid syntax`, + }, + { + Name: "uint", + Input: uint(7), + Expected: 7, + Err: "", + }, + { + Name: "uint64", + Input: uint64(7), + Expected: 7, + Err: "", + }, + { + Name: "int ok", + Input: 7, + Expected: 7, + Err: "", + }, + { + Name: "int not ok", + Input: -7, + Expected: 0, + Err: "SVN cannot be negative: -7", + }, + { + Name: "int64 ok", + Input: int64(7), + Expected: 7, + Err: "", + }, + { + Name: "int64 not ok", + Input: int64(-7), + Expected: 0, + Err: "SVN cannot be negative: -7", + }, + { + Name: "nil", + Input: nil, + Expected: 0, + Err: "", + }, + } { + t.Run(tv.Name, func(t *testing.T) { + ret, err := NewSVN(tv.Input, "exact-value") + exact := TaggedSVN(tv.Expected) + expected := SVN{&exact} + + if tv.Err != "" { + assert.EqualError(t, err, tv.Err) + } else { + assert.Equal(t, &expected, ret) + } + + retMin, err := NewSVN(tv.Input, "min-value") + min := TaggedMinSVN(tv.Expected) + expected = SVN{&min} + + if tv.Err != "" { + assert.EqualError(t, err, tv.Err) + } else { + assert.Equal(t, &expected, retMin) + } + }) + } + + in := TaggedSVN(7) + + _, err := NewSVN(in, "exact-value") + assert.NoError(t, err) + + _, err = NewSVN(&in, "exact-value") + assert.NoError(t, err) + + _, err = NewSVN(true, "exact-value") + assert.EqualError(t, err, "unexpected type for SVN exact-value: bool") + + inMin := TaggedMinSVN(7) + + _, err = NewSVN(inMin, "min-value") + assert.NoError(t, err) + + _, err = NewSVN(&inMin, "min-value") + assert.NoError(t, err) + + _, err = NewSVN(true, "min-value") + assert.EqualError(t, err, "unexpected type for SVN min-value: bool") + + _, err = NewSVN(true, "test") + assert.EqualError(t, err, "unknown SVN type: test") + + ret := MustNewSVN(7, "exact-value") + assert.NotNil(t, ret) + + assert.Panics(t, func() { MustNewSVN(true, "exact-value") }) +} + +func TestSVN_JSON(t *testing.T) { + var v SVN + + err := v.UnmarshalJSON([]byte(`{"type":"exact-value","value":2.3}`)) + assert.EqualError(t, err, "invalid SVN exact-value: json: cannot unmarshal number 2.3 into Go value of type comid.TaggedSVN") + + err = v.UnmarshalJSON([]byte(`{"type":"test","value":7}`)) + assert.EqualError(t, err, "unknown SVN type: test") + + err = v.UnmarshalJSON([]byte(`@@@`)) + assert.EqualError(t, err, "SVN decoding failure: invalid character '@' looking for beginning of value") + +} + +type testSVN uint64 + +func newTestSVN(val any) (*SVN, error) { + v := testSVN(7) + return &SVN{&v}, nil +} + +func (o testSVN) Type() string { + return "test-value" +} + +func (o testSVN) String() string { + return "test" +} + +func (o testSVN) Valid() error { + return nil +} + +type testSVNBadType struct { + testSVN +} + +func newTestSVNBadType(val any) (*SVN, error) { + v := testSVNBadType{testSVN(7)} + return &SVN{&v}, nil +} + +func (o testSVNBadType) Type() string { + return "min-value" +} + +func Test_RegisterSVNType(t *testing.T) { + err := RegisterSVNType(32, newTestSVN) + assert.EqualError(t, err, "tag 32 is already registered") + + err = RegisterSVNType(99995, newTestSVNBadType) + assert.EqualError(t, err, `SVN type with name "min-value" already exists`) + + err = RegisterSVNType(99995, newTestSVN) + require.NoError(t, err) + +} diff --git a/comid/ueid.go b/comid/ueid.go index 765d1b86..cf64ffa2 100644 --- a/comid/ueid.go +++ b/comid/ueid.go @@ -4,18 +4,17 @@ package comid import ( - "encoding/json" + "encoding/base64" "fmt" "github.com/veraison/eat" ) +const UEIDType = "ueid" + // UEID is an Unique Entity Identifier type UEID eat.UEID -// TaggedUEID is an alias to allow automatic tagging of an UEID type -type TaggedUEID UEID - func (o UEID) Empty() bool { return len(o) == 0 } @@ -28,25 +27,65 @@ func (o UEID) Valid() error { return nil } -// UnmarshalJSON deserializes the supplied string into the UEID target -func (o *UEID) UnmarshalJSON(data []byte) error { - var b []byte +func (o UEID) String() string { + return base64.StdEncoding.EncodeToString(o) +} + +// TaggedUEID is an alias to allow automatic tagging of an UEID type +type TaggedUEID UEID + +func NewTaggedUEID(val any) (*TaggedUEID, error) { + var ret TaggedUEID - if err := json.Unmarshal(data, &b); err != nil { - return err + if val == nil { + return &ret, nil } - u := UEID(b) + switch t := val.(type) { + case string: + b, err := base64.StdEncoding.DecodeString(t) + if err != nil { + return nil, fmt.Errorf("bad UEID: %w", err) + } - if err := u.Valid(); err != nil { - return err + ret = TaggedUEID(b) + case []byte: + ret = TaggedUEID(t) + case TaggedUEID: + ret = append(ret, t...) + case *TaggedUEID: + ret = append(ret, *t...) + case UEID: + ret = append(ret, t...) + case *UEID: + ret = append(ret, *t...) + case eat.UEID: + ret = append(ret, t...) + case *eat.UEID: + ret = append(ret, *t...) + default: + return nil, fmt.Errorf("unexpeted type for UEID: %T", t) } - *o = u + if err := ret.Valid(); err != nil { + return nil, err + } - return nil + return &ret, nil +} + +func (o TaggedUEID) Valid() error { + return UEID(o).Valid() +} + +func (o TaggedUEID) String() string { + return UEID(o).String() +} + +func (o TaggedUEID) Type() string { + return "ueid" } -func (o UEID) MarshalJSON() ([]byte, error) { - return json.Marshal([]byte(o)) +func (o TaggedUEID) Bytes() []byte { + return []byte(o) } diff --git a/comid/ueid_test.go b/comid/ueid_test.go new file mode 100644 index 00000000..a119ad44 --- /dev/null +++ b/comid/ueid_test.go @@ -0,0 +1,30 @@ +package comid + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_NewTaggedUEID(t *testing.T) { + ueid := UEID(TestUEID) + tagged := TaggedUEID(TestUEID) + bytes := MustHexDecode(t, TestUEIDString) + + for _, v := range []any{ + TestUEID, + &TestUEID, + ueid, + &ueid, + tagged, + &tagged, + bytes, + base64.StdEncoding.EncodeToString(bytes), + } { + ret, err := NewTaggedUEID(v) + require.NoError(t, err) + assert.Equal(t, []byte(TestUEID), ret.Bytes()) + } +} diff --git a/comid/uuid.go b/comid/uuid.go index c5c78d61..68fc630b 100644 --- a/comid/uuid.go +++ b/comid/uuid.go @@ -10,12 +10,11 @@ import ( "github.com/google/uuid" ) +const UUIDType = "uuid" + // UUID represents an Universally Unique Identifier (UUID, see RFC4122) type UUID uuid.UUID -// TaggedUUID is an alias to allow automatic tagging of a UUID type -type TaggedUUID UUID - // ParseUUID parses the supplied string into a UUID func ParseUUID(s string) (UUID, error) { v, err := uuid.Parse(s) @@ -64,3 +63,89 @@ func (o *UUID) UnmarshalJSON(data []byte) error { func (o UUID) MarshalJSON() ([]byte, error) { return json.Marshal(o.String()) } + +// TaggedUUID is an alias to allow automatic tagging of a UUID type +type TaggedUUID UUID + +func NewTaggedUUID(val any) (*TaggedUUID, error) { + var ret TaggedUUID + + switch t := val.(type) { + case string: + u, err := ParseUUID(t) + if err != nil { + return nil, fmt.Errorf("bad UUID: %w", err) + } + ret = TaggedUUID(u) + case []byte: + if len(t) != 16 { + return nil, fmt.Errorf( + "unexpected size for UUID: expected 16 bytes, found %d", + len(t), + ) + } + + copy(ret[:], t) + case TaggedUUID: + copy(ret[:], t[:]) + case *TaggedUUID: + copy(ret[:], (*t)[:]) + case UUID: + copy(ret[:], t[:]) + case *UUID: + copy(ret[:], (*t)[:]) + case uuid.UUID: + copy(ret[:], t[:]) + case *uuid.UUID: + copy(ret[:], (*t)[:]) + default: + return nil, fmt.Errorf("unexpected type for UUID: %T", t) + } + + if err := ret.Valid(); err != nil { + return nil, err + } + + return &ret, nil +} + +// String returns a string representation of the binary UUID +func (o TaggedUUID) String() string { + return UUID(o).String() +} + +func (o TaggedUUID) Valid() error { + return UUID(o).Valid() +} + +// Type returns a string containing type name. This is part of the +// ITypeChoiceValue implementation. +func (o TaggedUUID) Type() string { + return UUIDType +} + +// Bytes returns a []byte containing the raw UUID bytes +func (o TaggedUUID) Bytes() []byte { + return o[:] +} + +func (o TaggedUUID) MarshalJSON() ([]byte, error) { + temp := o.String() + return json.Marshal(temp) +} + +func (o *TaggedUUID) UnmarshalJSON(data []byte) error { + var temp string + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + + u, err := ParseUUID(temp) + if err != nil { + return fmt.Errorf("bad UUID: %w", err) + } + + *o = TaggedUUID(u) + + return nil +} diff --git a/comid/uuid_test.go b/comid/uuid_test.go new file mode 100644 index 00000000..5308854a --- /dev/null +++ b/comid/uuid_test.go @@ -0,0 +1,24 @@ +package comid + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUUID_JSON(t *testing.T) { + val := TaggedUUID(TestUUID) + expected := fmt.Sprintf(`"%s"`, val.String()) + + out, err := val.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, expected, string(out)) + + var outUUID TaggedUUID + + err = outUUID.UnmarshalJSON(out) + require.NoError(t, err) + assert.Equal(t, val, outUUID) +} diff --git a/corim/cbor.go b/corim/cbor.go index ec15e95f..17464d1d 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -4,6 +4,7 @@ package corim import ( + "fmt" "reflect" cbor "github.com/fxamacker/cbor/v2" @@ -18,13 +19,13 @@ var ( var ( CoswidTag = []byte{0xd9, 0x01, 0xf9} // 505() ComidTag = []byte{0xd9, 0x01, 0xfa} // 506() -) -func corimTags() cbor.TagSet { - corimTagsMap := map[uint64]interface{}{ + corimTagsMap = map[uint64]interface{}{ 32: comid.TaggedURI(""), } +) +func corimTags() cbor.TagSet { opts := cbor.TagOptions{ EncTag: cbor.EncTagRequired, DecTag: cbor.DecTagRequired, @@ -57,6 +58,28 @@ func initCBORDecMode() (dm cbor.DecMode, err error) { return decOpt.DecModeWithTags(corimTags()) } +func registerCORIMTag(tag uint64, t interface{}) error { + if _, exists := corimTagsMap[tag]; exists { + return fmt.Errorf("tag %d is already registered", tag) + } + + corimTagsMap[tag] = t + + var err error + + em, err = initCBOREncMode() + if err != nil { + return err + } + + dm, err = initCBORDecMode() + if err != nil { + return err + } + + return nil +} + func init() { if emError != nil { panic(emError) diff --git a/corim/entity.go b/corim/entity.go index cabea3d3..8a3169ac 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -4,7 +4,10 @@ package corim import ( + "encoding/json" + "errors" "fmt" + "unicode/utf8" "github.com/veraison/corim/comid" "github.com/veraison/corim/encoding" @@ -13,7 +16,7 @@ import ( // Entity stores an entity-map capable of CBOR and JSON serializations. type Entity struct { - EntityName string `cbor:"0,keyasint" json:"name"` + EntityName *EntityName `cbor:"0,keyasint" json:"name"` RegID *comid.TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` Roles Roles `cbor:"2,keyasint" json:"roles"` @@ -35,12 +38,13 @@ func (o *Entity) GetExtensions() extensions.IExtensionsValue { } // SetEntityName is used to set the EntityName field of Entity using supplied name -func (o *Entity) SetEntityName(name string) *Entity { +func (o *Entity) SetEntityName(name any) *Entity { + if o != nil { if name == "" { return nil } - o.EntityName = name + o.EntityName = MustNewStringEntityName(name) } return o } @@ -74,10 +78,14 @@ func (o *Entity) SetRoles(roles ...Role) *Entity { // Valid checks for validity of the fields within each Entity func (o Entity) Valid() error { - if o.EntityName == "" { + if o.EntityName == nil { return fmt.Errorf("invalid entity: empty entity-name") } + if err := o.EntityName.Valid(); err != nil { + return fmt.Errorf("invalid entity: %w", err) + } + if o.RegID != nil && o.RegID.Empty() { return fmt.Errorf("invalid entity: empty reg-id") } @@ -134,3 +142,233 @@ func (o Entities) Valid() error { } return nil } + +// EntityName encapsulates the name of the associated Entity. The CoRIM +// specification only allows for text (string) name, but this may be extended +// by other specifications. +type EntityName struct { + Value IEntityNameValue +} + +// NewEntityName creates a new EntityName of the specified type using the +// provided value. +func NewEntityName(val any, typ string) (*EntityName, error) { + factory, ok := entityNameValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unexpected entity name type: %s", typ) + } + + return factory(val) +} + +// MustNewEntityName is like NewEntityName, except it doesn't return an error, +// assuming that the provided value is valid. It panics if that isn't the case. +func MustNewEntityName(val any, typ string) *EntityName { + ret, err := NewEntityName(val, typ) + if err != nil { + panic(err) + } + + return ret +} + +// String returns the string representation of the EntityName +func (o EntityName) String() string { + return o.Value.String() +} + +// Valid returns nil if the underlying EntityName value is valid, or an error +// describing the problem otherwise. +func (o EntityName) Valid() error { + if o.Value == nil { + return errors.New("empty entity name") + } + + return o.Value.Valid() +} + +// MarshalCBOR serializes the EntityName into CBOR-encoded bytes. +func (o EntityName) MarshalCBOR() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + return em.Marshal(o.Value) +} + +// UnmarshalCBOR deserializes the EntityName from CBOR-encoded bytes. +func (o *EntityName) UnmarshalCBOR(data []byte) error { + if len(data) == 0 { + return errors.New("empty") + } + + majorType := (data[0] & 0xe0) >> 5 + if majorType == 3 { // text string + var text string + + if err := dm.Unmarshal(data, &text); err != nil { + return err + } + + name := StringEntityName(text) + o.Value = &name + + return nil + } + + return dm.Unmarshal(data, &o.Value) +} + +// MarshalJSON serializes the EntityName into a JSON object. +func (o EntityName) MarshalJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + if o.Value.Type() == extensions.StringType { + return json.Marshal(o.Value.String()) + } + + valueBytes, err := json.Marshal(o.Value) + if err != nil { + return nil, err + } + + value := encoding.TypeAndValue{ + Type: o.Value.Type(), + Value: valueBytes, + } + + return json.Marshal(value) +} + +// UnmarshalJSON deserializes EntityName from the provided JSON object. +func (o *EntityName) UnmarshalJSON(data []byte) error { + var text string + if err := json.Unmarshal(data, &text); err == nil { + *o = *MustNewStringEntityName(text) + return nil + } + + var tnv encoding.TypeAndValue + + if err := json.Unmarshal(data, &tnv); err != nil { + return fmt.Errorf("entity name decoding failure: %w", err) + } + + decoded, err := NewEntityName(nil, tnv.Type) + if err != nil { + return err + } + + if err := json.Unmarshal(tnv.Value, &decoded.Value); err != nil { + return fmt.Errorf( + "cannot unmarshal entity name: %w", + err, + ) + } + + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value + + return nil +} + +// IEntityNameValue is the interface implemented by all EntityName value types. +type IEntityNameValue interface { + extensions.ITypeChoiceValue +} + +// StringEntityName is a text string EntityName with no other contraints. This +// is the only EntityName value type defined by the CoRIM specification itself. +type StringEntityName string + +func NewStringEntityName(val any) (*EntityName, error) { + var ret StringEntityName + + if val == nil { + ret = StringEntityName("") + return &EntityName{&ret}, nil + } + + switch t := val.(type) { + case string: + ret = StringEntityName(t) + case []byte: + if !utf8.Valid(t) { + return nil, errors.New("bytes do not form a valid UTF-8 string") + } + + ret = StringEntityName(t) + default: + return nil, fmt.Errorf("unexpected type for string entity name: %T", t) + } + + return &EntityName{&ret}, nil +} + +func MustNewStringEntityName(val any) *EntityName { + ret, err := NewStringEntityName(val) + if err != nil { + panic(err) + } + + return ret +} + +func (o StringEntityName) String() string { + return string(o) +} + +func (o StringEntityName) Type() string { + return extensions.StringType +} + +func (o StringEntityName) Valid() error { + if o == "" { + return errors.New("empty entity-name") + } + + return nil +} + +// IEntityNameFactory defines the signature for the factory functions that may +// be registred using RegisterEntityNameType to provide a new implementation of +// the corresponding type choice. The factory function should create a new +// *EntityName with the underlying value created based on the provided input. +// The range of valid inputs is up to the specific type choice implementation, +// however it _must_ accept nil as one of the inputs, and return the Zero value +// for implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IEntityNameFactory func(any) (*EntityName, error) + +var entityNameValueRegister = map[string]IEntityNameFactory{ + extensions.StringType: NewStringEntityName, +} + +// RegisterEntityNameType registers a new IEntityNameValue implementation +// (created by the provided IEntityNameFactory) under the specified type name +// and CBOR tag. +func RegisterEntityNameType(tag uint64, factory IEntityNameFactory) error { + + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Value.Type() + if _, exists := entityNameValueRegister[typ]; exists { + return fmt.Errorf("entity name type with name %q already exists", typ) + } + + if err := registerCORIMTag(tag, nilVal.Value); err != nil { + return err + } + + entityNameValueRegister[typ] = factory + + return nil +} diff --git a/corim/entity_test.go b/corim/entity_test.go index 457b3770..aec1be92 100644 --- a/corim/entity_test.go +++ b/corim/entity_test.go @@ -4,6 +4,8 @@ package corim import ( + "errors" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -21,7 +23,7 @@ func TestEntity_Valid_uninitialized(t *testing.T) { func TestEntity_Valid_empty_name(t *testing.T) { tv := Entity{ - EntityName: "", + EntityName: MustNewStringEntityName(""), } err := tv.Valid() @@ -33,7 +35,7 @@ func TestEntity_Valid_non_nil_empty_URI(t *testing.T) { emptyRegID := comid.TaggedURI("") tv := Entity{ - EntityName: "ACME Ltd.", + EntityName: MustNewStringEntityName("ACME Ltd."), RegID: &emptyRegID, } @@ -46,7 +48,7 @@ func TestEntity_Valid_missing_roles(t *testing.T) { regID := comid.TaggedURI("http://acme.example") tv := Entity{ - EntityName: "ACME Ltd.", + EntityName: MustNewStringEntityName("ACME Ltd."), RegID: ®ID, } @@ -59,7 +61,7 @@ func TestEntity_Valid_unknown_role(t *testing.T) { regID := comid.TaggedURI("http://acme.example") tv := Entity{ - EntityName: "ACME Ltd.", + EntityName: MustNewStringEntityName("ACME Ltd."), RegID: ®ID, Roles: Roles{Role(666)}, } @@ -92,3 +94,185 @@ func TestEntities_Valid_empty(t *testing.T) { err := es.Valid() assert.EqualError(t, err, "entity at index 0: invalid entity: empty entity-name") } + +type testEntityName uint64 + +func newTestEntityName(val any) (*EntityName, error) { + if val == nil { + v := testEntityName(0) + return &EntityName{&v}, nil + } + + u, ok := val.(uint64) + if !ok { + return nil, errors.New("must be uint64") + } + + v := testEntityName(u) + return &EntityName{&v}, nil +} + +func (o testEntityName) Type() string { + return "test" +} + +func (o testEntityName) String() string { + return fmt.Sprint(uint64(o)) +} + +func (o testEntityName) Valid() error { + return nil +} + +type testEntityNameBadType struct { + testEntityName +} + +func newTestEntityNameBadType(val any) (*EntityName, error) { + v := testEntityNameBadType{testEntityName(7)} + return &EntityName{&v}, nil +} + +func (o testEntityNameBadType) Type() string { + return "string" +} + +func Test_RegisterEntityNameType(t *testing.T) { + err := RegisterEntityNameType(32, newTestEntityName) + assert.EqualError(t, err, "tag 32 is already registered") + + err = RegisterEntityNameType(99994, newTestEntityNameBadType) + assert.EqualError(t, err, `entity name type with name "string" already exists`) + + registerTestEntityNameType(t) +} + +// Since there only one, untagged, entity name type in the core spec, we use +// the test type define above in order to test the marshaling code works +// properly. Since global environment is not reset when running multiple tests, +// we cannot simply call RegisterEntityNameType() inside each test that relies +// on the test type, as that will cause the "tag already registered" error. On +// the other hand, we do not want to create inter-test dependencies by relying +// that the test registering the type is run before the others that rely on it. +// To get around this, use this global flag to only register the test type if a +// previous test hasn't already done so. +var testEntityNameTypeRegistered = false + +func registerTestEntityNameType(t *testing.T) { + if !testEntityNameTypeRegistered { + err := RegisterEntityNameType(99994, newTestEntityName) + require.NoError(t, err) + + testEntityNameTypeRegistered = true + } +} + +func TestEntityName_CBOR(t *testing.T) { + registerTestEntityNameType(t) + + for _, tv := range []struct { + Value any + Type string + ExpectedBytes []byte + ExpectedString string + }{ + { + Value: "test", + Type: "string", + ExpectedBytes: []byte{ + 0x64, // tstr(4) + 0x74, 0x65, 0x73, 0x74, // "test" + }, + ExpectedString: "test", + }, + { + Value: uint64(7), + Type: "test", + ExpectedBytes: []byte{ + 0xda, 0x0, 0x1, 0x86, 0x9a, // tag 99994 + 0x07, // unsigned int(7) + }, + ExpectedString: "7", + }, + } { + t.Run(tv.Type, func(t *testing.T) { + en, err := NewEntityName(tv.Value, tv.Type) + require.NoError(t, err) + + data, err := en.MarshalCBOR() + require.NoError(t, err) + + assert.Equal(t, tv.ExpectedBytes, data) + + var out EntityName + + err = out.UnmarshalCBOR(data) + require.NoError(t, err) + + assert.Equal(t, tv.ExpectedString, out.String()) + }) + } +} + +func TestEntityName_JSON(t *testing.T) { + registerTestEntityNameType(t) + + for _, tv := range []struct { + Value any + Type string + ExpectedBytes []byte + ExpectedString string + }{ + { + Value: "test", + Type: "string", + ExpectedBytes: []byte(`"test"`), + ExpectedString: "test", + }, + { + Value: uint64(7), + Type: "test", + ExpectedBytes: []byte(`{"type":"test","value":7}`), + ExpectedString: "7", + }, + } { + t.Run(tv.Type, func(t *testing.T) { + en, err := NewEntityName(tv.Value, tv.Type) + require.NoError(t, err) + + data, err := en.MarshalJSON() + require.NoError(t, err) + + assert.Equal(t, tv.ExpectedBytes, data) + + var out EntityName + + err = out.UnmarshalJSON(data) + require.NoError(t, err) + + assert.Equal(t, tv.ExpectedString, out.String()) + }) + } +} + +func Test_NewStringEntityName(t *testing.T) { + out, err := NewStringEntityName(nil) + require.NoError(t, err) + assert.EqualError(t, out.Valid(), "empty entity-name") + + out, err = NewStringEntityName([]byte("test")) + require.NoError(t, err) + assert.Equal(t, "test", out.String()) + + _, err = NewStringEntityName(7) + assert.EqualError(t, err, "unexpected type for string entity name: int") +} + +func Test_MustNewEntityName(t *testing.T) { + out := MustNewEntityName("test", "string") + assert.Equal(t, "test", out.String()) + + assert.Panics(t, func() { + MustNewEntityName(7, "int") + }) +} diff --git a/corim/extensions_test.go b/corim/extensions_test.go index 42deb71d..043d9a52 100644 --- a/corim/extensions_test.go +++ b/corim/extensions_test.go @@ -17,7 +17,7 @@ type TestExtensions struct { } func (o TestExtensions) ConstrainEntity(ent *Entity) error { - if ent.EntityName != "Futurama" { + if ent.EntityName.String() != "Futurama" { return errors.New(`EntityName must be "Futurama"`) // nolint:golint } @@ -78,7 +78,7 @@ func TestEntityExtensions_CBOR(t *testing.T) { err := cbor.Unmarshal(data, &ent) assert.NoError(t, err) - assert.Equal(t, ent.EntityName, "acme") + assert.Equal(t, ent.EntityName.String(), "acme") address, err := ent.Get("address") require.NoError(t, err) diff --git a/corim/role.go b/corim/role.go index 9bfc1276..7ef276f7 100644 --- a/corim/role.go +++ b/corim/role.go @@ -24,6 +24,35 @@ var ( } ) +// String returns the string representation of the Role. +func (o Role) String() string { + text, ok := roleToString[o] + if ok { + return text + } + + return fmt.Sprintf("Role(%d)", o) +} + +// RegisterRole creates a new Role association between the provided value and +// name. An error is returned if either clashes with any of the existing roles. +func RegisterRole(val int64, name string) error { + role := Role(val) + + if _, ok := roleToString[role]; ok { + return fmt.Errorf("role with value %d already exists", val) + } + + if _, ok := stringToRole[name]; ok { + return fmt.Errorf("role with name %q already exists", name) + } + + roleToString[role] = name + stringToRole[name] = role + + return nil +} + type Roles []Role func NewRoles() *Roles { @@ -44,7 +73,8 @@ func (o *Roles) Add(roles ...Role) *Roles { } func isRole(r Role) bool { - return r == RoleManifestCreator + _, ok := roleToString[r] + return ok } // Valid iterates over the range of individual roles to check for validity diff --git a/corim/role_test.go b/corim/role_test.go index 66df92b0..54b4433c 100644 --- a/corim/role_test.go +++ b/corim/role_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRoles_ToJSON_ok(t *testing.T) { @@ -77,3 +78,25 @@ func TestRoles_FromJSON_fail(t *testing.T) { assert.EqualError(t, err, tv.expectedErr) } } + +func Test_Role_String(t *testing.T) { + assert.Equal(t, "manifestCreator", RoleManifestCreator.String()) + assert.Equal(t, "Role(9999)", Role(9999).String()) +} + +func Test_RegisterRole(t *testing.T) { + err := RegisterRole(1, "owner") + assert.EqualError(t, err, "role with value 1 already exists") + + err = RegisterRole(3, "manifestCreator") + assert.EqualError(t, err, `role with name "manifestCreator" already exists`) + + err = RegisterRole(3, "owner") + assert.NoError(t, err) + + roles := NewRoles().Add(Role(3)) + + out, err := roles.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, `["owner"]`, string(out)) +} diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index c2b7ee86..918dde0f 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" @@ -182,7 +181,7 @@ func TestUnsignedCorim_Valid_ok(t *testing.T) { AddAttestVerifKey( comid.AttestVerifKey{ Environment: comid.Environment{ - Instance: comid.NewInstanceUUID(uuid.UUID(comid.TestUUID)), + Instance: comid.MustNewUUIDInstance(comid.TestUUID), }, VerifKeys: *comid.NewCryptoKeys(). Add( @@ -264,7 +263,7 @@ func TestUnsignedCorim_AddEntity_full(t *testing.T) { expected := UnsignedCorim{ Entities: &Entities{ Entity{ - EntityName: name, + EntityName: MustNewStringEntityName(name), Roles: Roles{role}, RegID: &taggedRegID, }, diff --git a/encoding/json.go b/encoding/json.go index e8193e4b..f9b24f89 100644 --- a/encoding/json.go +++ b/encoding/json.go @@ -317,3 +317,35 @@ func skipValue(decoder *json.Decoder) error { } return nil } + +// TypeAndValue stores a JSON object with two attributes: a string "type" +// and a generic "value" (string) defined by type. This type is used in +// a few places to implement the choice types that CBOR handles using tags. +type TypeAndValue struct { + Type string `json:"type"` + Value json.RawMessage `json:"value"` +} + +func (o *TypeAndValue) UnmarshalJSON(data []byte) error { + var temp struct { + Type string `json:"type"` + Value json.RawMessage `json:"value"` + } + + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + + if temp.Type == "" { + return errors.New("type not set") + } + + if len(temp.Value) == 0 { + return fmt.Errorf("no value provided for %s", temp.Type) + } + + o.Type = temp.Type + o.Value = temp.Value + + return nil +} diff --git a/encoding/json_test.go b/encoding/json_test.go index 132d1587..85eca9ff 100644 --- a/encoding/json_test.go +++ b/encoding/json_test.go @@ -119,3 +119,34 @@ func Test_skipValue(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "other", token) } + +func TestTypeAndValue_UnmarshalJSON(t *testing.T) { + for _, tv := range []struct { + Input string + Expected TypeAndValue + Err string + }{ + { + Input: `{"type": "test", "value": "test"}`, + Expected: TypeAndValue{Type: "test", Value: []byte(`"test"`)}, + }, + { + Input: `{"type": "test"}`, + Err: "no value provided for test", + }, + { + Input: `{"value": "test"}`, + Err: "type not set", + }, + } { + var out TypeAndValue + err := out.UnmarshalJSON([]byte(tv.Input)) + + if tv.Err != "" { + assert.EqualError(t, err, tv.Err) + } else { + assert.NoError(t, err) + assert.Equal(t, tv.Expected, out) + } + } +} diff --git a/extensions/typechoice.go b/extensions/typechoice.go new file mode 100644 index 00000000..5510c42a --- /dev/null +++ b/extensions/typechoice.go @@ -0,0 +1,36 @@ +package extensions + +import ( + "encoding/json" + + "github.com/veraison/corim/encoding" +) + +var StringType = "string" + +// ITypeChoiceValue is the interface that is implemented by all concrete type +// choice value types. Specific type choices define their own value interfaces +// that embed this one (and possibly include additional methods). +type ITypeChoiceValue interface { + // String returns the string representation of the ITypeChoiceValue. + String() string + // Valid returns an error if validation of the ITypeChoiceValue fails, + // or nil if it succeeds. + Valid() error + // Type returns the type name of this ITypeChoiceValue implementation. + Type() string +} + +func TypeChoiceValueMarshalJSON(v ITypeChoiceValue) ([]byte, error) { + valueBytes, err := json.Marshal(v) + if err != nil { + return nil, err + } + + value := encoding.TypeAndValue{ + Type: v.Type(), + Value: valueBytes, + } + + return json.Marshal(value) +} From 9f5f2a2fece5d567fc09a718711b5e72bc4dae90 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 2 Nov 2023 17:41:48 +0000 Subject: [PATCH 016/110] Document extensions Add documentation on how to use the newly added extensions mechanisms to implement CoRIM extensions. Signed-off-by: Sergei Trofimov --- README.md | 13 ++ extensions/README.md | 392 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 405 insertions(+) create mode 100644 extensions/README.md diff --git a/README.md b/README.md index 7dc87de8..8dd87998 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,16 @@ Before requesting a PR (and routinely during the dev/test cycle), you are encour make presubmit ``` and check its output to make sure your code coverage figures are in line with the set target and that there are no newly introduced lint problems. + +## Extending CoRIM/CoMID + +The CoRIM specification provides a mechanism for adding extensions to the base +CoRIM schema. The `corim` and `comid` structs which can be extended, embed an +`Extensions` object that allows registering a wrapper structure defining +extension fields. For field types that can be extended, i.e. `type choice`, +extensions can be implemented by calling an appropriate registration function +and giving it a new type or a value (for enums). + +Please see [extensions documentation](extensions/README.md) for details. + + diff --git a/extensions/README.md b/extensions/README.md new file mode 100644 index 00000000..aeffe0f7 --- /dev/null +++ b/extensions/README.md @@ -0,0 +1,392 @@ +[CoRIM +specification](https://datatracker.ietf.org/doc/draft-ietf-rats-corim/02) +may be extended by CoRIM Profiles documented in other specifications at well +defined points identified in the base CoRIM spec using CDDL extension sockets. +CoRIM profiles may + +1. Introduce new data fields to certain objects. +2. Further constrain allowed values for existing fields. +3. Introduce new type choices for fields whose values can be one of several + types. + +This implementation, likewise, allows dependent code to register extension +types. This is done via three distinct extension mechanisms: + +1. Structures that allow extensions embed an `Extensions` object, which allows + registering a user-provided struct. The user-provided struct can extend + their containing structures in two ways: + + - the fields of the user-provided struct become additional fields in + the containing structure. + - the user-provided struct may define additional constraints on the + containing structure by defining an appropriate validation method for it. + + This corresponds to the map-extension sockets in the spec. +2. Some fields may have values chosen from a set of pre-determined types (e.g., an + instance ID may be either a UUID or UEID). These (mostly) correspond to + type-choice sockets in the CoRIM spec. The set of allowed types for a field + may be extended by registering a factory function for a new type, using the + registration function associated with the type choice. +3. A couple of type-choice sockets (`$tag-rel-type-choice`, + `$corim-role-type-choice` and `$comid-role-type-choice`) define what, in + effect, are extensible enums. They allow providing additional values, rather + than types. This implementation provides registration functions for new + values for those types. + +> [!NOTE] +> CoRIM also "imports" CDDL from the CoSWID spec. Some of +> these CoSWID CDDL definitions also feature extension sockets. +> However, as they are defined in a different spec and are implemented +> in the [`veraison/swid`](https://github.com/veraison/swid) library, they cannot be extended +> using the extension feature provided in the CoRIM library. The extension +> support in CoRIM library is applicable ONLY to CoRIM and CoMID maps and +> type choices + + +## Map Extensions + +Map extensions allow extending CoRIM maps with additional keys, effectively +defining new fields for the corresponding structures. In the code base, these +can be identified by the embedded `Extensions` struct. These are + +- `comid.Comid` +- `comid.Entity` +- `comid.FlagsMap` +- `comid.Mval` +- `comid.Triples` +- `corim.Entity` +- `corim.Signer` +- `corim.UnsignedCorim` + +To extend the above types, you need to define a struct containing your +extensions and pass a pointer to an instance of that struct to the +`RegisterExtensions()` method of the corresponding instance of the type that is +being extended. This should be done as early as possible, before any marshaling +is performed. + +These types can be extended in two ways: by adding additional fields, and by +introducing additional constraints over existing fields. + +### Adding new fields + +To add new fields, simply add them to your extensions struct, ensuring that +the `cbor` and `json` tags on those fields are set correctly. As CoRIM +mandates integer keys, you must use the `keyasint` option for the `cbor` tag. + +To access the values of those fields, you can call the extended type instance's +`Extensions.Get()` passing in the name of the field you want to access. The +name can be either the Go struct's field name, the name specified in the `json` +tag, or (a string containing) the integer specified in the `cbor` tag. + +`Get()` returns an `interface{}`. There are equivalent `GetInt()`, +`GetString()`, etc. methods that perform the required conversions, and return +the value of the indicated type, along with possible errors. ("Must" versions +of these also exist, e.g. `MustGetString()`, that do not return an error and +instead call `panic()`). + +You can also get the pointer to your extension's instance itself by calling +the extended type instance's `GetExtensions()`. This returns an `interface{}`, so +you will need to type assert to be able to access the fields directly. + +### Introducing additional constraints + +To introduce new constraints, add a method called `Constrain(v *)` +to your extensions struct, where `` is the name of the type being +extended (one of the ones listed above) -- e.g. +`ConstrainComid(v *comid.Comid)` when extending `comid.Comid`. This method, if +it exists, will be invoked inside the extended type instance's `Valid()` +method, passing itself as the parameter. + +You do not need to define this method unless you actually want to enforce some +constraints (i.e., if you just want to define additional fields). + +### Example + +The following example illustrates how to implement a map extension by extending +`comid.Entity` with the following features: + +1. an optional "email" field +2. additional constraint on the existing "name" field that it contains a + valid UUID (note: since `NameEntry` is a type choice extensible, this can + also be done by defining a new value type for `NameEntry` -- see the + following section). + +```go +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/google/uuid" + "github.com/veraison/corim/comid" +) + +// the struct containing the extensions +type EntityExtensions struct { + // a string field extension + Email string `cbor:"-1,keyasint,omitempty" json:"email,omitempty"` +} + +// custom constraints for Entity +func (o EntityExtensions) ConstrainEntity(val *comid.Entity) error { + _, err := uuid.Parse(val.EntityName.String()) + if err != nil { + return fmt.Errorf("invalid UUID: %w", err) + } + + return nil +} + +var sampleText = ` +{ + "name": "31fb5abf-023e-4992-aa4e-95f9c1503bfa", + "regid": "https://acme.example", + "email": "info@acme.com", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] +} +` + +func main() { + var entity comid.Entity + entity.RegisterExtensions(&EntityExtensions{}) + + if err := json.Unmarshal([]byte(sampleText), &entity); err != nil { + log.Fatalf("ERROR: %s", err.Error()) + } + + if err := entity.Valid(); err != nil { + log.Fatalf("failed to validate: %s", err.Error()) + } else { + fmt.Println("validation succeeded") + } + + // obtain the extension field value via a generic getter + email := entity.Extensions.MustGetString("email") + fmt.Printf("entity email: %s\n", email) + + // retrive the extensions struct and get value via its field. + exts := entity.GetExtensions().(*EntityExtensions) + fmt.Printf("also entity email: %s\n", exts.Email) +} +``` + + +## Type Choice Extensions + +Type Choice extensions allow specifying alternative types for existing CoRIM +fields by defining a type that implements an appropriate interface and +registering it with a CBOR tag. + +A type choice struct contains a single field, `Value`, that contains the actual +object represented by the type choice. The `Value` implements an interface +that is specific to the type choice and is derived from `ITypeChoiceValue`: + +```go +type ITypeChoiceValue interface { + // String returns the string representation of the ITypeChoiceValue. + String() string + // Valid returns an error if validation of the ITypeChoiceValue fails, + // or nil if it succeeds. + Valid() error + // Type returns the type name of this ITypeChoiceValue implementation. + Type() string +} +``` + +The following is the full list of type choice structs: + +- `comid.ClassID` +- `comid.CryptoKey` +- `comid.EntityName` +- `comid.Group` +- `comid.Instance` +- `comid.Mkey` +- `comid.SVN` +- `corim.EntityName` + +To provide a new value type, the following is required: + +1. Define a type that implements the value interface for the type choice you + want to extend. This interface is called `IValue`, where `` is + the name of the type choice type(e.g. `IClassIDValue`). These interfaces + always embed `ITypeChoiceValue` and possibly define additional methods. +2. Create a factory function for your type, with the signature `func (any) + (*, error)`, where `` is the name of the type choice type that + will contain your value. (Note that the function must return a pointer to + the container type choice struct, _not_ to the value type you define.) This + function should create an instance of your value type from the provided + input and return a new type choice struct instance containing it. The range + of valid inputs is up to you, however it _must_ handle `null`, returning the + [zero-value](https://go.dev/ref/spec#The_zero_value) for your type in that + case. +3. Register your factory function with the CBOR tag for your new type by + passing it to the registration function corresponding to the type choice + struct. It will have the name `RegisterType`, where `` is the + name of the type choice struct that will contain your value (e.g. + `RegisterClassIDType`). + +### Example + +The following example illustrates how to add a new type choice value +implementation by extending the `CryptoKey` type to support DER values. + +```go +package main + +import ( + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "log" + + "github.com/veraison/corim/comid" +) + +// the CBOR tag to be used for the new type +var DerKeyTag = uint64(9999) + +// new implementation of ICryptoKeyValue type +type TaggedDerKey []byte + +// The factory function for the new type +func NewTaggedDerKey(k any) (*comid.CryptoKey, error) { + var b []byte + var err error + + if k == nil { + k = *new([]byte) + } + + switch t := k.(type) { + case []byte: + b = t + case string: + b, err = base64.StdEncoding.DecodeString(t) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("value must be a []byte; found %T", k) + } + + key := TaggedDerKey(b) + + return &comid.CryptoKey{Value: key}, nil +} + +func (o TaggedDerKey) String() string { + return base64.StdEncoding.EncodeToString(o) +} + +func (o TaggedDerKey) Valid() error { + _, err := o.PublicKey() + return err +} + +func (o TaggedDerKey) Type() string { + return "pkix-der-key" +} + +func (o TaggedDerKey) PublicKey() (crypto.PublicKey, error) { + if len(o) == 0 { + return nil, errors.New("key value not set") + } + + key, err := x509.ParsePKIXPublicKey(o) + if err != nil { + return nil, fmt.Errorf("unable to parse public key: %w", err) + } + + return key, nil +} + +var testKeyJSON = ` +{ + "type": "pkix-der-key", + "value": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8BlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==" +} +` + +func main() { + // register the factory function under the CBOR tag. + if err := comid.RegisterCryptoKeyType(DerKeyTag, NewTaggedDerKey); err != nil { + log.Fatal(err) + } + + var key comid.CryptoKey + + if err := json.Unmarshal([]byte(testKeyJSON), &key); err != nil { + log.Fatal(err) + } + + fmt.Printf("Decoded DER key: %x\n", key) +} +``` + + +## Enum extensions + +The following enum types may be extended with additional values: + +- `comid.Rel` +- `comid.Role` +- `corim.Role` + +This can be done by calling `RegisterRel` or `RegisterRole`, as appropriate, +and providing a new `uint64` value and corresponding `string` name. + +### Example + +```go +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/veraison/corim/comid" +) + +var sampleText = ` +{ + "name": "Acme Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "owner" + ] +} +` + +func main() { + // associate role value 4 with the name "owner" + comid.RegisterRole(4, "owner") + + var entity comid.Entity + + if err := json.Unmarshal([]byte(sampleText), &entity); err != nil { + log.Fatalf("ERROR: %s", err.Error()) + } + + if err := entity.Valid(); err != nil { + log.Fatalf("failed to validate: %s", err.Error()) + } else { + fmt.Println("validation succeeded") + } + + fmt.Println("roles:") + for _, role := range entity.Roles { + fmt.Printf("\t%s\n", role.String()) + } +} +``` From 9a6edcbca6d84fcb36b491870c0f54c94e30d51c Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Thu, 14 Dec 2023 13:42:45 +0000 Subject: [PATCH 017/110] handle nil input in UUID factory (#101) Fix #100 Signed-off-by: Thomas Fossati Co-authored-by: Thomas Fossati --- comid/measurement_test.go | 15 +++++++++++++++ comid/uuid.go | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/comid/measurement_test.go b/comid/measurement_test.go index c4f95e11..04321bde 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -516,3 +516,18 @@ func TestRegisterMkeyType(t *testing.T) { err = RegisterMkeyType(99996, newTestMkey) assert.NoError(t, err) } + +func TestMkey_UnmarshalJSON_regression_issue_100(t *testing.T) { + u := `31fb5abf-023e-4992-aa4e-95f9c1503bfa` + + tv := []byte(fmt.Sprintf(`{ "type": "uuid", "value": %q }`, u)) + + expected, err := NewMkeyUUID(u) + require.NoError(t, err) + + actual := &Mkey{} + err = actual.UnmarshalJSON(tv) + + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} diff --git a/comid/uuid.go b/comid/uuid.go index 68fc630b..ba439a0f 100644 --- a/comid/uuid.go +++ b/comid/uuid.go @@ -70,6 +70,10 @@ type TaggedUUID UUID func NewTaggedUUID(val any) (*TaggedUUID, error) { var ret TaggedUUID + if val == nil { + return &ret, nil + } + switch t := val.(type) { case string: u, err := ParseUUID(t) From d12f9400178fd4818fb9f2abaf88c3679c879967 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 14 Dec 2023 07:40:11 -0500 Subject: [PATCH 018/110] Refactor `cocli` documentation Fixes #102 Signed-off-by: Yogesh Deshpande --- cocli/COMID.md | 129 +++++ cocli/CORIM.md | 184 +++++++ cocli/COTS.md | 122 +++++ cocli/README.md | 477 ++---------------- cocli/cmd/cotsCreate.go | 12 +- cocli/cmd/cotsCreate_test.go | 14 +- cocli/data/comid/comid-dice-refval.cbor | Bin 0 -> 224 bytes .../templates/comid-cca-mult-refval.json | 0 .../templates/comid-cca-refval.json | 0 .../templates/comid-dice-refval.json | 0 .../templates/comid-psa-iakpub.json | 0 .../templates/comid-psa-integ-iakpub.json | 0 .../templates/comid-psa-refval.json | 0 cocli/data/corim/corim-full.cbor | Bin 0 -> 436 bytes .../corim/signed-corim-bad-signature.cbor | Bin 0 -> 922 bytes cocli/data/corim/signed-corim.cbor | Bin 0 -> 603 bytes .../data/{ => corim}/templates/corim-cca.json | 0 .../{ => corim}/templates/corim-full.json | 0 .../{ => corim}/templates/corim-mini.json | 0 .../data/{ => corim}/templates/meta-full.json | 0 .../data/{ => corim}/templates/meta-mini.json | 0 cocli/data/corim/unsigned-corim.cbor | Bin 0 -> 436 bytes cocli/data/cots/namedtastore.cbor | Bin 0 -> 766 bytes cocli/data/cots/rubbish.cbor | Bin 0 -> 1078 bytes .../{ => templates}/claims/exclclaim.json | 0 .../{ => templates}/claims/permclaim.json | 0 .../data/cots/{ => templates}/env/comid.json | 0 .../data/cots/{ => templates}/env/coswid.json | 0 .../{ => templates}/env/namedtastore.json | 0 .../data/cots/{ => templates}/env/vendor.json | 0 .../cots/{ => templates}/env/vendor2.json | 0 .../cots/{ => templates}/env/vendors.json | 0 cocli/data/cots/vendor.cbor | Bin 0 -> 674 bytes 33 files changed, 508 insertions(+), 430 deletions(-) create mode 100644 cocli/COMID.md create mode 100644 cocli/CORIM.md create mode 100644 cocli/COTS.md create mode 100644 cocli/data/comid/comid-dice-refval.cbor rename cocli/data/{ => comid}/templates/comid-cca-mult-refval.json (100%) rename cocli/data/{ => comid}/templates/comid-cca-refval.json (100%) rename cocli/data/{ => comid}/templates/comid-dice-refval.json (100%) rename cocli/data/{ => comid}/templates/comid-psa-iakpub.json (100%) rename cocli/data/{ => comid}/templates/comid-psa-integ-iakpub.json (100%) rename cocli/data/{ => comid}/templates/comid-psa-refval.json (100%) create mode 100644 cocli/data/corim/corim-full.cbor create mode 100644 cocli/data/corim/signed-corim-bad-signature.cbor create mode 100644 cocli/data/corim/signed-corim.cbor rename cocli/data/{ => corim}/templates/corim-cca.json (100%) rename cocli/data/{ => corim}/templates/corim-full.json (100%) rename cocli/data/{ => corim}/templates/corim-mini.json (100%) rename cocli/data/{ => corim}/templates/meta-full.json (100%) rename cocli/data/{ => corim}/templates/meta-mini.json (100%) create mode 100644 cocli/data/corim/unsigned-corim.cbor create mode 100644 cocli/data/cots/namedtastore.cbor create mode 100644 cocli/data/cots/rubbish.cbor rename cocli/data/cots/{ => templates}/claims/exclclaim.json (100%) rename cocli/data/cots/{ => templates}/claims/permclaim.json (100%) rename cocli/data/cots/{ => templates}/env/comid.json (100%) rename cocli/data/cots/{ => templates}/env/coswid.json (100%) rename cocli/data/cots/{ => templates}/env/namedtastore.json (100%) rename cocli/data/cots/{ => templates}/env/vendor.json (100%) rename cocli/data/cots/{ => templates}/env/vendor2.json (100%) rename cocli/data/cots/{ => templates}/env/vendors.json (100%) create mode 100644 cocli/data/cots/vendor.cbor diff --git a/cocli/COMID.md b/cocli/COMID.md new file mode 100644 index 00000000..50ea1db2 --- /dev/null +++ b/cocli/COMID.md @@ -0,0 +1,129 @@ + +# CoMIDs manipulation + +The `comid` subcommand allows you to create, display and validate CoMIDs. + +## Create + +Use the `comid create` subcommand to create a CBOR-encoded CoMID, passing its +JSON representation via the `--template` switch (or equivalently its `-t` shorthand): + +* Please inspect `comid` JSON templates as examples under `data/comid/templates` `comid-*.json` + +``` +$ cocli comid create --template data/comid/templates/comid-dice-refval.json +``` +On success, you should see something like the following printed to stdout: +``` +>> created "comid-dice-refval.cbor" from "comid-dice-refval.json" +``` + +The CBOR-encoded CoMID file is stored in the current working directory with a +name derived from its template. If you want, you can specify a different +target directory using the `--output-dir` command line switch (abbrev. `-o`) +``` +$ cocli comid create --template data/comid/templates/comid-dice-refval.json --output-dir /tmp +>> created "/tmp/comid-dice-refval.cbor" from "comid-dice-refval.json" +``` +Note that the output directory, as well as all its parent directories, MUST +pre-exist. + +You can also create multiple CoMIDs in one go. Suppose all your templates are +stored in the `templates/` folder: +``` +$ tree templates/ +templates/ +├── comid-dice-refval1.json +├── comid-dice-refval2.json +... +└── comid-dice-refvaln.json +``` +Then, you can use the `--template-dir` (abbrev. `-T`), and let the tool load, +validate, and CBOR-encode the templates one by one: +``` +$ cocli comid create --template-dir templates +>> created "comid-dice-refval1.cbor" from "templates/comid-dice-refval1.json" +>> created "comid-dice-refval2.cbor" from "templates/comid-dice-refval2.json" +... +>> created "comid-dice-refvaln.cbor" from "templates/comid-dice-refvaln.json" +``` + +You can specify both the `-T` and `-t` switches as many times as needed, and +even combine them in one invocation: +``` +$ cocli comid create -T comid-templates/ \ + -T comid-templates-aux/ \ + -t extra-comid.json \ + -t yet-another-comid.json \ + -o /var/spool/comid +``` + +**NOTE** that since the output file name is deterministically generated from the +template file name, all the template files (when from different directories) +MUST have different base names. + + +## Display + +Use the `comid display` subcommand to print to stdout one or more CBOR-encoded +CoMIDs in human readable (JSON) format. + +You can supply individual files using the `--file` switch (abbrev. `-f`), or +directories that may (or may not) contain CoMID files using the `--dir` switch +(abbrev. `-d`). Only valid CoMIDs will be displayed, and any decoding or +validation error will be printed alongside the corresponding file name. + +For example: +``` +$ cocli comid display --file data/comid/comid-dice-refval.cbor +``` +provided the `comid-dice-refval.cbor` file contains valid CoMID, would print something like: +``` +>> [comid-dice-refval.cbor] +{ + "tag-identity": { + "id": "1d5a8c7c-1c70-4c56-937e-3c5713ae5a83" + }, + "triples": {} +[...] +} +``` +While a `data/comid/` folder with the following contents: +``` +$ tree data/comid/ +data/comid/ +├── rubbish.cbor +├── 1.cbor +└── 2.cbor +``` +could be inspected in one go using: +``` +$ cocli comid display --dir data/comid/ +``` +which would output something like: +``` +>> failed displaying "comids.d/rubbish.cbor": CBOR decoding failed: EOF +>> [data/comid/1.cbor] +{ + "tag-identity": { + "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" + }, +[...] +} +>> [data/comid/2.cbor] +{ + "tag-identity": { + "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" + }, +[...] +} +Error: 1/3 display(s) failed +``` + +One of more files and directories can be supplied in the same invocation, e.g.: +``` +$ cocli comid display -f m1.cbor \ + -f comids.d/m2.cbor \ + -d /var/spool/comids \ + -d yet-another-comid-folder/ +``` \ No newline at end of file diff --git a/cocli/CORIM.md b/cocli/CORIM.md new file mode 100644 index 00000000..fdd40255 --- /dev/null +++ b/cocli/CORIM.md @@ -0,0 +1,184 @@ +# CoRIMs manipulation + +The `corim` subcommand allows you to create, display, sign, verify CoRIMs or submit +a CoRIM using the [Veraison provisioning API](https://github.com/veraison/docs/tree/main/api/endorsement-provisioning). +It also provides a means to extract as-is the embedded CoSWIDs, CoMIDs and CoTSs and save +them as separate files. + +## Create + +Use the `corim create` subcommand to create a CBOR-encoded, unsigned CoRIM, by +passing its JSON representation via the `--template` switch (or equivalently its `-t` shorthand) +together with the CBOR-encoded CoMIDs, CoSWIDs and/or CoTS to be embedded. + +* Please inspect `corim` JSON templates as examples under `data/corim/templates` `corim-*.json` + +``` +$ cocli corim create --template data/corim/templates/corim-full.json --comid data/comid/comid-dice-refval.cbor --coswid data/coswid/1.cbor --cots data/cots/vendor.cbor +``` +On success, you should see something like the following printed to stdout: +``` +>> created "corim-full.cbor" from "corim-full.json" +``` + +The CBOR-encoded CoRIM file is stored in the current working directory with a +name derived from its template. If you want, you can specify a different +file name using the `--output` command line switch (abbrev. `-o`): +``` +$ cocli corim create -t data/corim/templates/corim-full.json -m data/comid/comid-dice-refval.cbor -s data/coswid/1.cbor -c data/cots/c1.cbor -o unsigned-corim.cbor +>> created "unsigned-corim.cbor" from "corim-full.json" +``` + +CoMIDs, CoSWIDs and CoTSs can be either supplied as individual files, using the +`--comid` (abbrev. `-m`), `--coswid` (abbrev. `-s`) and `--cots` (abbrev. `-c`) switches respectively, or +as "per-folder" blocks using the `--comid-dir` (abbrev. `-M`), `--coswid-dir` and `--cots-dir` +(abbrev. `-C`) switch. For example: +``` +$ cocli corim create --template data/corim/templates/corim-full.json --comid-dir data/comid/cbor/ +``` + +Creation will fail if *any* of the inputs is non conformant. For example, if +`data/comid/cbor/` contains an invalid CoMID file `rubbish.cbor`, an attempt to create a +CoRIM: +``` +$ cocli corim create -t data/corim/templates/corim-full.json -M data/comid/cbor/ +``` +will fail with: +``` +Error: error loading CoMID from data/comid/cbor/rubbish.cbor: EOF +``` + +## Sign + +Use the `corim sign` subcommand to cryptographically seal the unsigned CoRIM +supplied via the `--file` switch (abbrev. `-f`). The signature is produced +using the key supplied via the `--key` switch (abbrev. `-k`), which is expected +to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. On success, the +resulting COSE Sign1 payload is saved to file whose name can be controlled using +the `--output` switch (abbrev. `-o`). A CoRIM Meta template in JSON format must +also be provided using the `--meta` switch (abbrev.`-m`). + +* Please inspect the `data/corim/templates` directory for `meta` JSON templates. + +For example, with the default output file: +``` +$ cocli corim sign --file corim.cbor --key ec-p256.jwk --meta meta.json +>> "corim.cbor" signed and saved to "signed-corim.cbor" +``` +Or, the same but with a custom output file: +``` +$ cocli corim sign --file data/corim/corim-full.cbor \ + --key data/keys/ec-p256.jwk \ + --meta data/corim/templates/meta-full.json \ + --output /var/spool/signed-corim.cbor +>> "corim-full.cbor" signed and saved to "/var/spool/signed-corim.cbor" +``` + +## Verify + +Use the `corim verify` subcommand to cryptographically verify the signed CoRIM +supplied via the `--file` switch (abbrev. `-f`). The signature is checked +using the key supplied via the `--key` switch (abbrev. `-k`), which is expected +to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. For example: +``` +$ cocli corim verify --file data/corim/signed-corim.cbor --key data/keys/ec-p256.jwk +>> "signed-corim.cbor" verified +``` + +Verification can fail either because the cryptographic processing fails or +because the signed payload or protected headers are themselves invalid. For example: +``` +$ cocli corim verify --file data/corim/signed-corim-bad-signature.cbor --key data/keys/ec-p256.jwk +``` +will give +``` +Error: error verifying signed-corim-bad-signature.cbor with key ec-p256.jwk: verification failed ecdsa.Verify +``` + +## Display + +Use the `corim display` subcommand to print to stdout a signed CoRIM in human +readable (JSON) format. + +You must supply the file you want to display using the `--file` switch (abbrev. +`-f`). Only a valid CoRIM will be displayed, and any occurring decoding or +validation errors will be printed instead. + +The output has two logical sections: one for Meta and one for the (unsigned) +CoRIM: +``` +$ cocli corim display --file data/corim/signed-corim.cbor +Meta: +{ + "signer": { + "name": "ACME Ltd signing key", + "uri": "https://acme.example/signing-key.pub" + }, +[...] +} +Corim: +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "tags": [ + "2QH...", +[...] + ] +} +``` + +By default, the embedded CoMID, CoSWID and CoTS tags are not expanded, and what you +will see is the base64 encoding of their CBOR serialisation. If you want to +peek at the tags' content, supply the `--show-tags` (abbrev. `-v`) switch, which +will add a further Tags section with one entry per each expanded tag: +``` +$ cocli corim display --file data/corim/signed-corim.cbor --show-tags +Meta: +{ +[...] +} +Corim: +{ +[...] +} +Tags: +>> [ 0 ] +{ + "tag-identity": { + "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" + }, +[...] +} +>> [ 1 ] +{ + "tag-identity": { + "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" + }, +[...] +} +>> [ 2 ] +{ + "tag-id": "com.acme.rrd2013-ce-sp1-v4-1-5-0", +[...] +} +``` + +## Extract CoSWIDs, CoMIDs and CoTSs + +Use the `corim extract` subcommand to extract the embedded CoMIDs, CoSWIDs and CoTSs +from a signed CoRIM. + +You must supply a signed CoRIM file using the `--file` switch (abbrev. `-f`) and +an optional output folder (default is the current working directory) using the +`--output-dir` switch (abbrev. `-o`). Make sure that the output directory as +well as any parent folder exists prior to issuing the command. + +On success, the found CoMIDs, CoSWIDs, CoTS are saved in CBOR format: +``` +$ cocli corim extract --file data/corim/signed-corim.cbor --output-dir output.d/ +$ tree output.d/ +output.d/ +├── 000000-comid.cbor +├── 000001-comid.cbor +├── 000002-coswid.cbor +└── 000003-cots.cbor +``` \ No newline at end of file diff --git a/cocli/COTS.md b/cocli/COTS.md new file mode 100644 index 00000000..fc094820 --- /dev/null +++ b/cocli/COTS.md @@ -0,0 +1,122 @@ + +## CoTSs manipulation + +The `cots` subcommand allows you to create, display and validate CoTSs. + +### Create + +Use the `cots create` subcommand to create a CBOR-encoded CoTS. The `environment` switch takes in a JSON template specifiying the environments that are valid for the keys specified and the `tas` switch takes in a directory of trust anchors files: + +* Please inspect `data/cots/templates` JSON templates as examples for `environment` and `claims` + + +``` +$ cocli cots create --environment data/cots/env/vendor.json --tafile data/cots/shared_ta.ta +``` +On success, you should see something like the following printed to stdout: +``` +>> created "vendor.cbor" +``` + +The CBOR-encoded CoTS file is stored in the current working directory with a +name derived from its environment template. If you want, you can specify a different +target directory and file name using the `--output` command line switch (abbrev. `-o`) +``` +$ cocli cots create --environment data/cots/env/vendor.json --tafile data/cots/shared_ta.ta --output /tmp/myCots.cbor +>> created "/tmp/myCots.cbor" +``` +Note that the output directory, as well as all its parent directories, MUST pre-exist. + +### Display + +Use the `cots display` subcommand to print to stdout one or more CBOR-encoded +CoTSs in human readable (JSON) format. + +You can supply individual files using the `--file` switch (abbrev. `-f`), or +directories that may (or may not) contain CoTS files using the `--dir` switch +(abbrev. `-d`). Only valid CoTSs will be displayed, and any decoding or +validation error will be printed alongside the corresponding file name. + +For example: +``` +$ cocli cots display --file vendor.cbor +``` +provided the `vendor.cbor` file contains valid CoTS, would print something like: +``` +>> [vendor.cbor] +{ + "environments": [ + { + "environment": { + "class": { + "vendor": "Zesty Hands, Inc." + } + } + } + ], + "keys": { + "tas": [ + { + "format": 1, + "data": "ooICejCCAnYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATjUaoQOSQHpL0DfKC8EVTQ5wHwZ085yyxPkhBpLOu+7B0nl33FYWV1Hg4je/37FTbpmohFkUKWYd81z8C/K1DMBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzCCAgEwPjELMAkGA1UEBgwCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHTAbBgNVBAMMFEV4YW1wbGUgVHJ1c3QgQW5jaG9yoIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M=" + } + ] + } +} + +``` +While a `data/cots` folder with the following contents: +``` +$ tree cots/ +cots/ +├── rubbish.cbor +├── namedtastore.cbor +├── vendor.cbor +``` +could be inspected in one go using: +``` +$ cocli cots display --dir data/cots/ +``` +which would output something like: +``` +>> [data/cots/namedtastore.cbor] +{ + "environments": [ + { + "namedtastore": "Miscellaneous TA Store" + } + ], + "keys": { + "tas": [ + { + "format": 1, + "data": "ooIC1TCCAtEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN0f5kzywEzZOYbaV23O3N8cku39JoLNjlHPwECbXDDWp0LpAO1z248/hoy6UW/TZMTPPR/93XwHsG16mSFy8XBBSKhM/5gJWjvDbW7qUY1peNm9cfYDCCAlwwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yoIIB+jCCAZ+gAwIBAgIUEBuTRGXAEEVEHhu4xafAnqm+qYgwCgYIKoZIzj0EAwIwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMB4XDTIyMDUxOTE1MTMwOFoXDTMyMDUxNjE1MTMwOFowXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzdH+ZM8sBM2TmG2ldtztzfHJLt/SaCzY5Rz8BAm1ww1qdC6QDtc9uPP4aMulFv02TEzz0f/d18B7BtepkhcvF6M/MD0wHQYDVR0OBBYEFIqEz/mAlaO8NtbupRjWl42b1x9gMAsGA1UdDwQEAwIChDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQC2cf43f3PPlCO6/dxv40ftIgxxToKHF72UzENv7+y4ygIhAIGtC/r6SGaFMaP7zD2EloBuIXTtyWu8Hwl+YGdXRY93" + } + ] + } +} +>> failed displaying "data/cots/rubbish.cbor": CBOR decoding failed: cbor: cannot unmarshal primitives into Go value of type cots.ConciseTaStore +>> [data/cots/vendor.cbor] +{ + "environments": [ + { + "environment": { + "class": { + "vendor": "Zesty Hands, Inc." + } + } + } + ], + "keys": { + "tas": [ + { + "format": 1, + "data": "ooICejCCAnYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATjUaoQOSQHpL0DfKC8EVTQ5wHwZ085yyxPkhBpLOu+7B0nl33FYWV1Hg4je/37FTbpmohFkUKWYd81z8C/K1DMBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzCCAgEwPjELMAkGA1UEBgwCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHTAbBgNVBAMMFEV4YW1wbGUgVHJ1c3QgQW5jaG9yoIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M=" + } + ] + } +} + +Note: One of more files and directories can be supplied in the same invocation, using -f and -d directive: + +``` diff --git a/cocli/README.md b/cocli/README.md index 28c1395b..4f3e3fce 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -19,408 +19,68 @@ To get a list of the supported shells, do: ``` $ cocli completion --help ``` +# CoRIM Handling +A CoRIM consists of either one or more CoMID and/or CoSWID and/or CoTS. The below +picture and instructions provides a step by step approach to handle CoRIMs. + +``` mermaid +flowchart TD + subgraph CORIM["CoRIM"] + subgraph CoMID["\n"] + CM1["CoMID-1"] + CM2["CoMID-2"] + + CM3["CoMID-N"] + CM4["COMID COMMANDS \n cocli comid create \n cocli comid display"] + CM1 -.- CM2 + CM2 -.- CM3 + CM3 -.- CM4 + end + subgraph CoMID["Blank1"] + CSW1["CoSWID-1"] + CSW2["CoSWID-2"] + CSW3["CoSWID-N"] + + CSW1 -.- CSW2 + CSW2 -.- CSW3 + + end + + subgraph CoMID["Blank3"] + CS1["CoTS-1"] + CS2["CoTS-2"] + + CS3["CoTS-N"] + CS4["COTS COMMANDS \n cocli cots create \n cocli cots display"] + CS1 -.- CS2 + + CS2 -.- CS3 + CS3 -.- CS4 + end +end +CORIM ---> CMD +subgraph CMD["CORIM COMMANDS \n + 1.cocli corim create \n 2.cocli corim display \n 3.cocli corim sign \n4.cocli corim verify\n5.cocli corim extract\n 6.cocli corim submit"] +end -## CoMIDs manipulation - -The `comid` subcommand allows you to create, display and validate CoMIDs. - -### Create - -Use the `comid create` subcommand to create a CBOR-encoded CoMID, passing its -JSON representation[1](#templates-ex) via the `--template` switch (or -equivalently its `-t` shorthand): -``` -$ cocli comid create --template t1.json -``` -On success, you should see something like the following printed to stdout: -``` ->> created "t1.cbor" from "t1.json" -``` - -The CBOR-encoded CoMID file is stored in the current working directory with a -name derived from its template. If you want, you can specify a different -target directory using the `--output-dir` command line switch (abbrev. `-o`) -``` -$ cocli comid create --template t1.json --output-dir /tmp ->> created "/tmp/t1.cbor" from "t1.json" -``` -Note that the output directory, as well as all its parent directories, MUST -pre-exist. - -You can also create multiple CoMIDs in one go. Suppose all your templates are -stored in the `templates/` folder: -``` -$ tree templates/ -templates/ -├── t1.json -├── t2.json -... -└── tn.json -``` -Then, you can use the `--template-dir` (abbrev. `-T`), and let the tool load, -validate, and CBOR-encode the templates one by one: -``` -$ cocli comid create --template-dir templates ->> created "t1.cbor" from "templates/t1.json" ->> created "t2.cbor" from "templates/t2.json" -... ->> created "tn.cbor" from "templates/tn.json" -``` - -You can specify both the `-T` and `-t` switches as many times as needed, and -even combine them in one invocation: -``` -$ cocli comid create -T comid-templates/ \ - -T comid-templates-aux/ \ - -t extra-comid.json \ - -t yet-another-comid.json \ - -o /var/spool/comid -``` - -**NOTE** that since the output file name is deterministically generated from the -template file name, all the template files (when from different directories) -MUST have different base names. - - -### Display - -Use the `comid display` subcommand to print to stdout one or more CBOR-encoded -CoMIDs in human readable (JSON) format. - -You can supply individual files using the `--file` switch (abbrev. `-f`), or -directories that may (or may not) contain CoMID files using the `--dir` switch -(abbrev. `-d`). Only valid CoMIDs will be displayed, and any decoding or -validation error will be printed alongside the corresponding file name. - -For example: -``` -$ cocli comid display --file m1.cbor -``` -provided the `m1.cbor` file contains valid CoMID, would print something like: -``` ->> [m1.cbor] -{ - "lang": "en-GB", - "tag-identity": { - "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "https://acme.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } -[...] -``` -While a `comids.d` folder with the following contents: -``` -$ tree comids.d/ -comids.d/ -├── rubbish.cbor -├── valid-comid-1.cbor -└── valid-comid-2.cbor -``` -could be inspected in one go using: -``` -$ cocli comid display --dir comids.d/ -``` -which would output something like: -``` ->> failed displaying "comids.d/rubbish.cbor": CBOR decoding failed: EOF ->> [comids.d/valid-comid-1.cbor] -{ - "tag-identity": { - "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" - }, -[...] -} ->> [comids.d/valid-comid-2.cbor] -{ - "tag-identity": { - "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" - }, -[...] -} -Error: 1/3 display(s) failed ``` -One of more files and directories can be supplied in the same invocation, e.g.: -``` -$ cocli comid display -f m1.cbor \ - -f comids.d/m2.cbor \ - -d /var/spool/comids \ - -d yet-another-comid-folder/ -``` +## CoMIDs manipulation +The instructions to manipulate CoMIDs are documented [here](COMID.md) ## CoTSs manipulation +The instructions to manipulate CoTSs are documented [here](COTS.md) -The `cots` subcommand allows you to create, display and validate CoTSs. - -### Create - -Use the `cots create` subcommand to create a CBOR-encoded CoTS. The `environment` switch takes in a JSON template specifiying the environments that are valid for the keys specified and the `tas` switch takes in a directory of trust anchors files: -``` -$ cocli cots create --environment c1.json --tas tas_dir -``` -On success, you should see something like the following printed to stdout: -``` ->> created "c1.cbor" -``` - -The CBOR-encoded CoTS file is stored in the current working directory with a -name derived from its environment template. If you want, you can specify a different -target directory and file name using the `--output` command line switch (abbrev. `-o`) -``` -$ cocli cots create --environment c1.json --tas tas_dir --output /tmp/myCots.cbor ->> created "/tmp/myCots.cbor" -``` -Note that the output directory, as well as all its parent directories, MUST pre-exist. - -### Display - -Use the `cots display` subcommand to print to stdout one or more CBOR-encoded -CoTSs in human readable (JSON) format. - -You can supply individual files using the `--file` switch (abbrev. `-f`), or -directories that may (or may not) contain CoTS files using the `--dir` switch -(abbrev. `-d`). Only valid CoTSs will be displayed, and any decoding or -validation error will be printed alongside the corresponding file name. - -For example: -``` -$ cocli cots display --file c1.cbor -``` -provided the `c1.cbor` file contains valid CoTS, would print something like: -``` ->> [c1.cbor] -{ - "tag-identity": { - "id": "ab0f44b1-bfdc-4604-ab4a-30f80407ebcc", - "version": 5 - }, - "environments": [ - { - "environment": { - "class": { - "vendor": "Worthless Sea, Inc." - } - } - } - ], - "keys": { - "tas": [ - { - "format": 2, - "data": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErYoMAdqe2gJT3CvCcifZxyE9+N8T6Jy5zbeo5LYtnOipmi1wXA9/gNtlwAbRCRQitH/GEcvUaGlzPZxIOITV/g==" - } - ] - } -} -``` -While a `cots` folder with the following contents: -``` -$ tree cots/ -cots/ -├── rubbish.cbor -├── valid-cots-1.cbor -├── valid-cots-2.cbor -``` -could be inspected in one go using: -``` -$ cocli cots display --dir cots/ -``` -which would output something like: -``` ->> failed displaying "cots/rubbish.cbor": CBOR decoding failed: EOF ->> [cots/valid-cots-1.cbor] -{ - "tag-identity": { - "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" - }, -[...] -} ->> [cots/valid-cots-2.cbor] -{ - "tag-identity": { - "id": "ab0f44b1-bfdc-4604-ab4a-30f80407ebcc" - }, -[...] -} -Error: 1/3 display(s) failed -``` - -One of more files and directories can be supplied in the same invocation, e.g.: -``` -$ cocli cots display -f c1.cbor \ - -f cots/c2.cbor \ - -d /var/spool/cots \ - -d yet-another-cots-folder/ -``` - +## CoSWID manipulation +Tooling to manipulate `CoSWID` is not currently available under Project Veraison. +However CoSWID can be part of CoRIM by constructing CoSWID CBOR by other indistry available +tools such as [swid-tools](https://github.com/usnistgov/swid-tools) and including them +as mentioned under [CORIM Construction](CORIM.md) ## CoRIMs manipulation +The instructions to manipulate CoRIMs are documented [here](CORIM.md) -The `corim` subcommand allows you to create, display, sign, verify CoRIMs or submit -a CoRIM using the [Veraison provisioning API](https://github.com/veraison/docs/tree/main/api/endorsement-provisioning). -It also provides a means to extract as-is the embedded CoSWIDs, CoMIDs and CoTSs and save -them as separate files. - -### Create - -Use the `corim create` subcommand to create a CBOR-encoded, unsigned CoRIM, by -passing its JSON representation[1](#templates-ex) via the -`--template` switch (or equivalently its `-t` shorthand) together with the -CBOR-encoded CoMIDs, CoSWIDs and/or CoTS to be embedded. For example: -``` -$ cocli corim create --template c1.json --comid m1.cbor --coswid s1.cbor --cots c1.cbor -``` -On success, you should see something like the following printed to stdout: -``` ->> created "c1.cbor" from "c1.json" -``` - -The CBOR-encoded CoRIM file is stored in the current working directory with a -name derived from its template. If you want, you can specify a different -file name using the `--output` command line switch (abbrev. `-o`): -``` -$ cocli corim create -t r1.json -m m1.cbor -s s1.cbor -c c1.cbor -o my.cbor ->> created "my.cbor" from "r1.json" -``` - -CoMIDs, CoSWIDs and CoTSs can be either supplied as individual files, using the -`--comid` (abbrev. `-m`), `--coswid` (abbrev. `-s`) and `--cots` (abbrev. `-c`) switches respectively, or -as "per-folder" blocks using the `--comid-dir` (abbrev. `-M`), `--coswid-dir` and `--cots-dir` -(abbrev. `-C`) switch. For example: -``` -$ cocli corim create --template c1.json --comid-dir comids.d/ -``` - -Creation will fail if *any* of the inputs is non conformant. For example, if -`comids.d` contains an invalid CoMID file `rubbish.cbor`, an attempt to create a -CoRIM: -``` -$ cocli corim create -t c1.json -M comids.d/ -``` -will fail with: -``` -Error: error loading CoMID from comids.d/rubbish.cbor: EOF -``` - -### Sign - -Use the `corim sign` subcommand to cryptographically seal the unsigned CoRIM -supplied via the `--file` switch (abbrev. `-f`). The signature is produced -using the key supplied via the `--key` switch (abbrev. `-k`), which is expected -to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. On success, the -resulting COSE Sign1 payload is saved to file whose name can be controlled using -the `--output` switch (abbrev. `-o`). A CoRIM Meta[1](#templates-ex) -template in JSON format must also be provided using the `--meta` switch (abbrev. -`-m`). For example, with the default output file: -``` -$ cocli corim sign --file corim.cbor --key ec-p256.jwk --meta meta.json ->> "corim.cbor" signed and saved to "signed-corim.cbor" -``` -Or, the same but with a custom output file: -``` -$ cocli corim sign --file corim.cbor \ - --key ec-p256.jwk \ - --meta meta.json \ - --output /var/spool/signed-corim.cbor ->> "corim.cbor" signed and saved to "/var/spool/signed-corim.cbor" -``` - -### Verify - -Use the `corim verify` subcommand to cryptographically verify the signed CoRIM -supplied via the `--file` switch (abbrev. `-f`). The signature is checked -using the key supplied via the `--key` switch (abbrev. `-k`), which is expected -to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. For example: -``` -$ cocli corim verify --file signed-corim.cbor --key ec-p256.jwk ->> "corim.cbor" verified -``` - -Verification can fail either because the cryptographic processing fails or -because the signed payload or protected headers are themselves invalid. For example: -``` -$ cocli corim verify --file signed-corim-bad-signature.cbor --key ec-p256.jwk -``` -will give -``` -Error: error verifying signed-corim-bad-signature.cbor with key ec-p256.jwk: verification failed ecdsa.Verify -``` - -### Display - -Use the `corim display` subcommand to print to stdout a signed CoRIM in human -readable (JSON) format. - -You must supply the file you want to display using the `--file` switch (abbrev. -`-f`). Only a valid CoRIM will be displayed, and any occurring decoding or -validation errors will be printed instead. - -The output has two logical sections: one for Meta and one for the (unsigned) -CoRIM: -``` -$ cocli corim display --file signed-corim.cbor -Meta: -{ - "signer": { - "name": "ACME Ltd signing key", - "uri": "https://acme.example/signing-key.pub" - }, -[...] -} -Corim: -{ - "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", - "tags": [ - "2QH...", -[...] - ] -} -``` - -By default, the embedded CoMID, CoSWID and CoTS tags are not expanded, and what you -will see is the base64 encoding of their CBOR serialisation. If you want to -peek at the tags' content, supply the `--show-tags` (abbrev. `-v`) switch, which -will add a further Tags section with one entry per each expanded tag: -``` -$ cocli corim display --file signed-corim.cbor --show-tags -Meta: -{ -[...] -} -Corim: -{ -[...] -} -Tags: ->> [ 0 ] -{ - "tag-identity": { - "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" - }, -[...] -} ->> [ 1 ] -{ - "tag-identity": { - "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" - }, -[...] -} ->> [ 2 ] -{ - "tag-id": "com.acme.rrd2013-ce-sp1-v4-1-5-0", -[...] -} -``` -### Submit +## CoRIM Submission to Veraison Use the `corim submit` subcommand to upload a CoRIM using the Veraison provisioning API. The CoRIM file containing the CoRIM data in CBOR format is supplied via the @@ -430,11 +90,11 @@ Further, it is required to supply the media type of the content via the `--media-type` switch (abbrev. `-m`) ``` $ cocli corim submit \ - --corim-file unsigned-corim.cbor \ + --corim-file data/corim/unsigned-corim.cbor \ --api-server "https://veraison.example/endorsement-provisioning/v1/submit" \ --media-type "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" ->> "corim.cbor" submit ok +>> "unsigned-corim.cbor" submit ok ``` #### Remote Service Authentication @@ -447,33 +107,6 @@ path (usually `~/.config/cocli/config.yaml` on XDG-compliant systems). Please see `./data/config/example-config.yaml` file for details of the configuration that needs to be provided. -### Extract CoSWIDs, CoMIDs and CoTSs - -Use the `corim extract` subcommand to extract the embedded CoMIDs, CoSWIDs and CoTSs -from a signed CoRIM. - -You must supply a signed CoRIM file using the `--file` switch (abbrev. `-f`) and -an optional output folder (default is the current working directory) using the -`--output-dir` switch (abbrev. `-o`). Make sure that the output directory as -well as any parent folder exists prior to issuing the command. - -On success, the found CoMIDs, CoSWIDs, CoTS are saved in CBOR format: -``` -$ cocli corim extract --file signed-corim.cbor --output-dir output.d/ -$ tree output.d/ -output.d/ -├── 000000-comid.cbor -├── 000001-comid.cbor -├── 000002-coswid.cbor -└── 000003-cots.cbor -``` - - - -1: A few examples of CoMID, CoRIM, CoTS, and Meta JSON -templates can be found in the [data/templates](data/templates) folder. - - ## Visual Synopsis of the Available Commands ```mermaid diff --git a/cocli/cmd/cotsCreate.go b/cocli/cmd/cotsCreate.go index eb3c52dd..af44bca9 100644 --- a/cocli/cmd/cotsCreate.go +++ b/cocli/cmd/cotsCreate.go @@ -54,6 +54,16 @@ func NewCotsCreateCtsCmd() *cobra.Command { --tas=tas_dir \ --cas=cas_dir \ --output=cots.cbor + + Alternatively one can specify individual TA files (in DER Format) or CA files (binary, DER-encoded X.509 Certificate) + + cocli cots create --environment=env-template.json \ + --purpose=eat \ + --purpose=corim \ + --permclaims=claims-template.json \ + --tafile=tas_dir \ + --cafile=cas_dir \ + --output=cots.cbor `, RunE: func(cmd *cobra.Command, args []string) error { @@ -100,7 +110,7 @@ func NewCotsCreateCtsCmd() *cobra.Command { &cotsCreateCtsTaDirs, "tas", "t", []string{}, "a directory containing binary DER-encoded trust anchor files", ) cmd.Flags().StringArrayVarP( - &cotsCreateCtsTaFiles, "tafile", "", []string{}, "a DER-encoded trust anchor file", + &cotsCreateCtsTaFiles, "tafile", "f", []string{}, "a DER-encoded trust anchor file", ) cmd.Flags().StringArrayVarP( diff --git a/cocli/cmd/cotsCreate_test.go b/cocli/cmd/cotsCreate_test.go index 21eff6a0..8faa976d 100644 --- a/cocli/cmd/cotsCreate_test.go +++ b/cocli/cmd/cotsCreate_test.go @@ -61,7 +61,7 @@ func Test_CotsCreateCtsCmd_too_many_ids(t *testing.T) { "--output=output.cbor", "--uuid", "--id=some_tag_identity", - "--environment=../data/cots/env/vendor.json", + "--environment=../data/cots/templates/env/vendor.json", "--tafile=../data/cots/shared_ta.ta", } cmd.SetArgs(args) @@ -76,7 +76,7 @@ func Test_CotsCreateCtsCmd_invalid_uuid(t *testing.T) { args := []string{ "--output=output.cbor", "--uuid-str=NotAUuid", - "--environment=../data/cots/env/vendor.json", + "--environment=../data/cots/templates/env/vendor.json", "--tafile=../data/cots/shared_ta.ta", } cmd.SetArgs(args) @@ -104,7 +104,7 @@ func Test_CotsCreateCtsCmd_loading_permclaims_template_fail(t *testing.T) { args := []string{ "--output=output.cbor", - "--environment=../data/cots/env/vendor.json", + "--environment=../data/cots/templates/env/vendor.json", "--permclaims=nonexistent.json", "--tafile=../data/cots/shared_ta.ta", } @@ -119,7 +119,7 @@ func Test_CotsCreateCtsCmd_loading_exclclaims_template_fail(t *testing.T) { args := []string{ "--output=output.cbor", - "--environment=../data/cots/env/vendor.json", + "--environment=../data/cots/templates/env/vendor.json", "--exclclaims=nonexistent.json", "--tafile=../data/cots/shared_ta.ta", } @@ -134,9 +134,9 @@ func Test_CotsCreateCtsCmd_ok(t *testing.T) { args := []string{ "--output=output.cbor", - "--environment=../data/cots/env/vendor.json", - "--exclclaims=../data/cots/claims/exclclaim.json", - "--permclaims=../data/cots/claims/permclaim.json", + "--environment=../data/cots/templates/env/vendor.json", + "--exclclaims=../data/cots/templates/claims/exclclaim.json", + "--permclaims=../data/cots/templates/claims/permclaim.json", "--tafile=../data/cots/shared_ta.ta", } cmd.SetArgs(args) diff --git a/cocli/data/comid/comid-dice-refval.cbor b/cocli/data/comid/comid-dice-refval.cbor new file mode 100644 index 0000000000000000000000000000000000000000..29a7c2aadd465c9e15367ddb3f9ad97f145feaad GIT binary patch literal 224 zcmZ3)xR4=0Hmavarobm`a-B`M@VcmGmW2#WO$!;8Fx*fLxSN*vfm6eUaawl7yybpI zMNG+VzRt`HEDVhc85c9&WYXYbYHVVRP;gmgob~0j$cjp5z2Lj4g~xx*KC^y%yhl@{ znr`0WB{!v+xnL&#zg6>D+1P91%oS{HY6@1Tn38-9nHdoVGBGuQ3~YDmKdRJVYseN? zea_~C{gYD8G{dJ4s`pm$-%B`hUiCOgO&IIBtWxW%r&Zi{Bu?+GIqkOmvo-Vky&H6< NDctItb-5a&nS!Vxonqu9;zyiLQx3nxU?RiAAcefl*?LS*k^% zk*P@%BgncZ(M4Ym9N1cP+qc(h`^$Q>{_lQCCRI(oZ?;vnJXP{7Wp1pH09gdIC$T73 zFF8L~zo0l#KQq5X-;iYy!$GOUa~=$g2cPC7c*o!I{UgR_>`pRG2T$fNlea7 Kg*l9=kr4p+52+jg literal 0 HcmV?d00001 diff --git a/cocli/data/corim/signed-corim-bad-signature.cbor b/cocli/data/corim/signed-corim-bad-signature.cbor new file mode 100644 index 0000000000000000000000000000000000000000..8116a8c37173a82225f4d23b70116effd29bace1 GIT binary patch literal 922 zcmZWnO-~b16rFZ}6c)OWh2bN4LEDh%beQSTGN_prAz++f5CHkGpHfC=bU@*`?~MG_w$YU?Ci^FA|r>~_gk*b zL$}jb1J}D{Tb&?fmM|(2!cw_@7CsL{zq_ES-1eNT)90Sw za%hCsO>oewT5hAn`TRz;H&m&-~1 zEeEq!#L5}SvNp1N0;P7{0eYi#4FaR=@m0{ih|}L*<1(AWhz@&;B6ss1lsW^WqUaf5 zi}Km&r`vGYuXiG|EXNI@yRd#SesiA(PCJZWojy)iwQVcMd0}43YnGuHb9GHA7zIa} z)A>Q(DR6z>u+BSeGHkXJh$943Pz9(NuvdAZ80hTx=<@|$*6w(! z-{q>?2~{or43~c(wE!l$@t4k0HLUgT|D}E>%p&+cAdIC?cHm`1h2qTg zyv)3Gh3wQy#v2MH86_nJ#a88bl^K%Wupp9g z%QA+5nD7^0+|D{lPdv$Se)2<`Ka7nLk8d*mTEw`JAwV{&r$(m0Cv0+^O}Oy7sAiUh z3{6c78I~~IPz|`7miU2F!-a8LcEr5penv%1$!@;R%nU3HjSCqUGu~v<;9_cQVvJC5 zS!JB{<+R9(N@u;`yQzi8f6YF#etWz}Q>2=1-r^-UrJ1>4CjP%w^I6&0YvRlmY;9@^ zR;QSfd<>Zx5e70bHGvFlcj`Z?)L?7K7FT`F=7ar{QqDBPrw^+4R`K6UIC5U~I7m$x z>$$8_>#L_#+;=2S@2xrQw*0d-^ZUIUbfziX>YH`ImKo@{MGQ9-DjZ-TRghSenpXk~ zD^Nfe>nA5C87C%MnChAtCYk7(7^E5MT9{a*>KYg&rkJH#BpR8TBr$@ldlFsr^}vCx zMYnx>t+v0cH|ziImt<1a(Klp4 zix$?##SEFSc+~?&W)5;BGBq+rIIQ-ZvTmM&YIXEQwcUNWhA#~-zBHP6WYU4R(Q_|z pv~1iI^v1}M@l*EcnmC7|`;R)eZI|7|x6th0q=Mh9OzSHWL;#3(?x6qx literal 0 HcmV?d00001 diff --git a/cocli/data/templates/corim-cca.json b/cocli/data/corim/templates/corim-cca.json similarity index 100% rename from cocli/data/templates/corim-cca.json rename to cocli/data/corim/templates/corim-cca.json diff --git a/cocli/data/templates/corim-full.json b/cocli/data/corim/templates/corim-full.json similarity index 100% rename from cocli/data/templates/corim-full.json rename to cocli/data/corim/templates/corim-full.json diff --git a/cocli/data/templates/corim-mini.json b/cocli/data/corim/templates/corim-mini.json similarity index 100% rename from cocli/data/templates/corim-mini.json rename to cocli/data/corim/templates/corim-mini.json diff --git a/cocli/data/templates/meta-full.json b/cocli/data/corim/templates/meta-full.json similarity index 100% rename from cocli/data/templates/meta-full.json rename to cocli/data/corim/templates/meta-full.json diff --git a/cocli/data/templates/meta-mini.json b/cocli/data/corim/templates/meta-mini.json similarity index 100% rename from cocli/data/templates/meta-mini.json rename to cocli/data/corim/templates/meta-mini.json diff --git a/cocli/data/corim/unsigned-corim.cbor b/cocli/data/corim/unsigned-corim.cbor new file mode 100644 index 0000000000000000000000000000000000000000..261316a478dd8340be49375f1f27c8554f4e9882 GIT binary patch literal 436 zcmZ3+5D*jo;)~l^C+UeNInGahX!D1$G2-z}#$SsV7cvCMM)lOl6!?TquCoajUKiEO zvXG&vX(7WBh8wB@cheF-aB8?PPRov%x7^RDh$-34*O{4tg`sgF<6_2}Od4EFjZKUZ z3NEXRv%Z`bSyAb%7koFh@c6IUXV!0z_h^b#)6HAF5a&nS!Vxonqu9;zyiLQx3nxU?RiAAcefl*?LS*k^% zk*P@%BgncZ(M4Ym9N1cP+qc(h`^$Q>{_lQCCRI(oZ?;vnJXP{7Wp1pH09gdIC$T73 zFF8L~zo0l#KQq5X-;iYy!$GOUa~=$g2cPC7c*o!I{UgR_>`pRG2T$fNlea7 Kg*l9=kr4p+52+jg literal 0 HcmV?d00001 diff --git a/cocli/data/cots/namedtastore.cbor b/cocli/data/cots/namedtastore.cbor new file mode 100644 index 0000000000000000000000000000000000000000..2bbbcc174136a5fc0abcfddf0f54a576907cc1dc GIT binary patch literal 766 zcmZ3))VPqj%r~<*IW;FIF)ua0v{)g;Q6ab_zbKV$Awy#mVL~jD$b=w`Ee`K6qD)!gR$LI6K|97t+sAjvqa+0{dIEzSE%lV%TQy1?syY_CW z#I@ZQ4CKUljf@OT4J{2#4UG*fqQrTPL0mH^m&Uf! zE_N5&8`y%QO_q;E3`bOR10zkAp9L7mOf3fdAdWC2<9`-b17;wF90$yv3+hZX$L?>HcnbZR+Qs)yIpduF{>_e4KqZZ9xqtogNNY7*{QHb;%e00( U#gextv-ikz)+MBeyY`m@0M@k!{{R30 literal 0 HcmV?d00001 diff --git a/cocli/data/cots/rubbish.cbor b/cocli/data/cots/rubbish.cbor new file mode 100644 index 0000000000000000000000000000000000000000..336db72d08713f4f738746202ae0aabe61c3bb8e GIT binary patch literal 1078 zcmaFAe=pPf{d<9kxvaPX0u@3W6@p9hi&EKu!VC~u#z>}{i-1g!Vgnc*X&}tTt_9?I z0I@9-BO3>t!_3Ie?8LwV6o;S`Af>}{b~2F41>!P@D4f)TurhQYv<#49;e@ccvPwX* zd_Zgq5do4JrTjv_ciE|kl8JHSc8k!m!8(2h%a~XrUW>7ATZ6E9j3>Y`|2Dab?BFo1jMrwNF z2BsTXeimRXg0mGrh%3wpN)#-t2FzdvdUP{;G8niqDS~okArPC_7oVS^3}ir1K9F(; zVkMqJKZuApgaVSzAV~;<=m)a7A$*TCAZ-X?14&yrD^IZm!p;U#@|<-E>EW(GRyhFI C(GRTv literal 0 HcmV?d00001 diff --git a/cocli/data/cots/claims/exclclaim.json b/cocli/data/cots/templates/claims/exclclaim.json similarity index 100% rename from cocli/data/cots/claims/exclclaim.json rename to cocli/data/cots/templates/claims/exclclaim.json diff --git a/cocli/data/cots/claims/permclaim.json b/cocli/data/cots/templates/claims/permclaim.json similarity index 100% rename from cocli/data/cots/claims/permclaim.json rename to cocli/data/cots/templates/claims/permclaim.json diff --git a/cocli/data/cots/env/comid.json b/cocli/data/cots/templates/env/comid.json similarity index 100% rename from cocli/data/cots/env/comid.json rename to cocli/data/cots/templates/env/comid.json diff --git a/cocli/data/cots/env/coswid.json b/cocli/data/cots/templates/env/coswid.json similarity index 100% rename from cocli/data/cots/env/coswid.json rename to cocli/data/cots/templates/env/coswid.json diff --git a/cocli/data/cots/env/namedtastore.json b/cocli/data/cots/templates/env/namedtastore.json similarity index 100% rename from cocli/data/cots/env/namedtastore.json rename to cocli/data/cots/templates/env/namedtastore.json diff --git a/cocli/data/cots/env/vendor.json b/cocli/data/cots/templates/env/vendor.json similarity index 100% rename from cocli/data/cots/env/vendor.json rename to cocli/data/cots/templates/env/vendor.json diff --git a/cocli/data/cots/env/vendor2.json b/cocli/data/cots/templates/env/vendor2.json similarity index 100% rename from cocli/data/cots/env/vendor2.json rename to cocli/data/cots/templates/env/vendor2.json diff --git a/cocli/data/cots/env/vendors.json b/cocli/data/cots/templates/env/vendors.json similarity index 100% rename from cocli/data/cots/env/vendors.json rename to cocli/data/cots/templates/env/vendors.json diff --git a/cocli/data/cots/vendor.cbor b/cocli/data/cots/vendor.cbor new file mode 100644 index 0000000000000000000000000000000000000000..8996a35a32efaf6d3a32955c4a071879b5de8064 GIT binary patch literal 674 zcmZ3))VPpwA;Ut(!l=~Zl1c@S#JrSZ9R<(4WIeWp42?~UkxX@qnwY8#nwZKAA`OJu z*tOa`&e<|CvT=YJ%#7^JP7ExM16K)Hs<1EF%UrWyk6_4!=ZqiH{Vh-H_)ikZ)Oo${ zjja0g+M|i7rE+}A)qj7Bn!TLW;X2W2TH<}v^9S~82b^IMVT^G-xn_f#R$=UQfdv%@ zL_I$;+5;_RG_W(|HsEAq4rO8EVG0d46foceakzNcT`LlE3vyBoWeuc3BFsD@P!WZY zqSE3L1;@PPjQpYnO^kaDnix|SFf%bSF^Mu*d> z21)a?00WAt#eg5g5oTok&%$cJ45X05k=dQWz=cVHn@!z;?fA_~{qhucU%Vodceq~Y!YqZM~ Date: Tue, 19 Dec 2023 13:01:09 -0500 Subject: [PATCH 019/110] Address some of the minor comments Signed-off-by: Yogesh Deshpande --- cocli/COMID.md | 2 +- cocli/README.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cocli/COMID.md b/cocli/COMID.md index 50ea1db2..15b4f8c9 100644 --- a/cocli/COMID.md +++ b/cocli/COMID.md @@ -120,7 +120,7 @@ which would output something like: Error: 1/3 display(s) failed ``` -One of more files and directories can be supplied in the same invocation, e.g.: +One or more files and directories can be supplied in the same invocation, e.g.: ``` $ cocli comid display -f m1.cbor \ -f comids.d/m2.cbor \ diff --git a/cocli/README.md b/cocli/README.md index 4f3e3fce..a38411cd 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -20,8 +20,7 @@ To get a list of the supported shells, do: $ cocli completion --help ``` # CoRIM Handling -A CoRIM consists of either one or more CoMID and/or CoSWID and/or CoTS. The below -picture and instructions provides a step by step approach to handle CoRIMs. +This document provides step-by-step instructions for how to use the `cocli` tool to manipulate CoRIMs, CoMIDs and CoTS. ``` mermaid flowchart TD From c657e547e186c53818e507d54dcd6f9a5ba18429 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Wed, 20 Dec 2023 06:39:13 -0500 Subject: [PATCH 020/110] Tidy up the top level picture! Signed-off-by: Yogesh Deshpande --- cocli/README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/cocli/README.md b/cocli/README.md index a38411cd..15400092 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -24,44 +24,46 @@ This document provides step-by-step instructions for how to use the `cocli` tool ``` mermaid flowchart TD + subgraph COCLI["COCLI COMMANDS"] + style COCLI fill:#ffffff, stroke:#333,stroke-width:4px + subgraph CORIMCMD["CORIM COMMANDS \n + cocli corim create \n cocli corim display \n cocli corim sign \n cocli corim verify\n cocli corim extract\n cocli corim submit"] + end + subgraph COTSCMD["COTS COMMANDS \n cocli cots create \n cocli cots display"] + end + subgraph COMIDCMD["COMID COMMANDS \n cocli comid create \n cocli comid display"] + end + end + CORIM ---> CORIMCMD subgraph CORIM["CoRIM"] subgraph CoMID["\n"] CM1["CoMID-1"] CM2["CoMID-2"] CM3["CoMID-N"] - CM4["COMID COMMANDS \n cocli comid create \n cocli comid display"] CM1 -.- CM2 CM2 -.- CM3 - CM3 -.- CM4 + CM3 ---> COMIDCMD end subgraph CoMID["Blank1"] CSW1["CoSWID-1"] CSW2["CoSWID-2"] CSW3["CoSWID-N"] - CSW1 -.- CSW2 CSW2 -.- CSW3 end subgraph CoMID["Blank3"] - CS1["CoTS-1"] - CS2["CoTS-2"] - CS3["CoTS-N"] - CS4["COTS COMMANDS \n cocli cots create \n cocli cots display"] + CS2["CoTS-2"] + CS1["CoTS-1"] + CS1 -.- CS2 - CS2 -.- CS3 - CS3 -.- CS4 + CS3 ---> COTSCMD end end -CORIM ---> CMD -subgraph CMD["CORIM COMMANDS \n - 1.cocli corim create \n 2.cocli corim display \n 3.cocli corim sign \n4.cocli corim verify\n5.cocli corim extract\n 6.cocli corim submit"] -end - ``` ## CoMIDs manipulation From 0cc2f05b375253d5cb3392356620991b7af64a46 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Wed, 20 Dec 2023 06:44:39 -0500 Subject: [PATCH 021/110] Tidy up daigram Signed-off-by: Yogesh Deshpande --- cocli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cocli/README.md b/cocli/README.md index 15400092..34c8021f 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -34,7 +34,7 @@ flowchart TD subgraph COMIDCMD["COMID COMMANDS \n cocli comid create \n cocli comid display"] end end - CORIM ---> CORIMCMD + CORIM ----> CORIMCMD subgraph CORIM["CoRIM"] subgraph CoMID["\n"] CM1["CoMID-1"] From 81738fc10751ffd3d0caaec7c109447661a6da55 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Wed, 20 Dec 2023 11:49:40 +0000 Subject: [PATCH 022/110] Update README.md --- cocli/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cocli/README.md b/cocli/README.md index 34c8021f..91b9ea37 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -34,7 +34,9 @@ flowchart TD subgraph COMIDCMD["COMID COMMANDS \n cocli comid create \n cocli comid display"] end end + CORIM ----> CORIMCMD + subgraph CORIM["CoRIM"] subgraph CoMID["\n"] CM1["CoMID-1"] From 85d506d51c1d3ca4ba024b6a73c37bdb8fca21f0 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 11 Jan 2024 12:12:12 +0000 Subject: [PATCH 023/110] Update README.md --- cocli/README.md | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/cocli/README.md b/cocli/README.md index 91b9ea37..529d5535 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -22,50 +22,49 @@ $ cocli completion --help # CoRIM Handling This document provides step-by-step instructions for how to use the `cocli` tool to manipulate CoRIMs, CoMIDs and CoTS. -``` mermaid +```mermaid flowchart TD - subgraph COCLI["COCLI COMMANDS"] + subgraph COCLI["COCLI COMMANDS"] style COCLI fill:#ffffff, stroke:#333,stroke-width:4px subgraph CORIMCMD["CORIM COMMANDS \n cocli corim create \n cocli corim display \n cocli corim sign \n cocli corim verify\n cocli corim extract\n cocli corim submit"] end - subgraph COTSCMD["COTS COMMANDS \n cocli cots create \n cocli cots display"] - end subgraph COMIDCMD["COMID COMMANDS \n cocli comid create \n cocli comid display"] end - end - - CORIM ----> CORIMCMD - - subgraph CORIM["CoRIM"] - subgraph CoMID["\n"] - CM1["CoMID-1"] - CM2["CoMID-2"] + subgraph COTSCMD["COTS COMMANDS \n cocli cots create \n cocli cots display"] + end + end + CORIM ---> CORIMCMD +subgraph CORIM["CoRIM"] + subgraph CoMID["COMIDs\n"] CM3["CoMID-N"] + CM2["CoMID-2"] + CM1["CoMID-1"] CM1 -.- CM2 CM2 -.- CM3 - CM3 ---> COMIDCMD - end - subgraph CoMID["Blank1"] + CM3 ---> COMIDCMD + end + + subgraph CoSWID["CoSWID\n"] CSW1["CoSWID-1"] CSW2["CoSWID-2"] CSW3["CoSWID-N"] CSW1 -.- CSW2 - CSW2 -.- CSW3 - + CSW2 -.- CSW3 end - subgraph CoMID["Blank3"] + subgraph CoTS["COTS\n"] CS3["CoTS-N"] CS2["CoTS-2"] CS1["CoTS-1"] - CS1 -.- CS2 CS2 -.- CS3 CS3 ---> COTSCMD end + end + ``` ## CoMIDs manipulation From 178f6ee10b93fc8850a6d1017981d3b10ed99ecd Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 11 Jan 2024 10:04:41 -0500 Subject: [PATCH 024/110] Address more comments Signed-off-by: Yogesh Deshpande --- cocli/README.md | 435 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 431 insertions(+), 4 deletions(-) diff --git a/cocli/README.md b/cocli/README.md index 529d5535..09776d66 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -19,7 +19,7 @@ To get a list of the supported shells, do: ``` $ cocli completion --help ``` -# CoRIM Handling +# Cocli Command Snapshot This document provides step-by-step instructions for how to use the `cocli` tool to manipulate CoRIMs, CoMIDs and CoTS. ```mermaid @@ -68,19 +68,446 @@ end ``` ## CoMIDs manipulation -The instructions to manipulate CoMIDs are documented [here](COMID.md) + +The `comid` subcommand allows you to create, display and validate CoMIDs. + +### Create + +Use the `comid create` subcommand to create a CBOR-encoded CoMID, passing its +JSON representation via the `--template` switch (or equivalently its `-t` shorthand): + +* Please inspect `comid` JSON templates as examples under `data/comid/templates` `comid-*.json` + +``` +$ cocli comid create --template data/comid/templates/comid-dice-refval.json +``` +On success, you should see something like the following printed to stdout: +``` +>> created "comid-dice-refval.cbor" from "comid-dice-refval.json" +``` + +The CBOR-encoded CoMID file is stored in the current working directory with a +name derived from its template. If you want, you can specify a different +target directory using the `--output-dir` command line switch (abbrev. `-o`) +``` +$ cocli comid create --template data/comid/templates/comid-dice-refval.json --output-dir /tmp +>> created "/tmp/comid-dice-refval.cbor" from "comid-dice-refval.json" +``` +Note that the output directory, as well as all its parent directories, MUST +pre-exist. + +You can also create multiple CoMIDs in one go. Suppose all your templates are +stored in the `templates/` folder: +``` +$ tree templates/ +templates/ +├── comid-dice-refval1.json +├── comid-dice-refval2.json +... +└── comid-dice-refvaln.json +``` +Then, you can use the `--template-dir` (abbrev. `-T`), and let the tool load, +validate, and CBOR-encode the templates one by one: +``` +$ cocli comid create --template-dir templates +>> created "comid-dice-refval1.cbor" from "templates/comid-dice-refval1.json" +>> created "comid-dice-refval2.cbor" from "templates/comid-dice-refval2.json" +... +>> created "comid-dice-refvaln.cbor" from "templates/comid-dice-refvaln.json" +``` + +You can specify both the `-T` and `-t` switches as many times as needed, and +even combine them in one invocation: +``` +$ cocli comid create -T comid-templates/ \ + -T comid-templates-aux/ \ + -t extra-comid.json \ + -t yet-another-comid.json \ + -o /var/spool/comid +``` + +**NOTE** that since the output file name is deterministically generated from the +template file name, all the template files (when from different directories) +MUST have different base names. + + +### Display + +Use the `comid display` subcommand to print to stdout one or more CBOR-encoded +CoMIDs in human readable (JSON) format. + +You can supply individual files using the `--file` switch (abbrev. `-f`), or +directories that may (or may not) contain CoMID files using the `--dir` switch +(abbrev. `-d`). Only valid CoMIDs will be displayed, and any decoding or +validation error will be printed alongside the corresponding file name. + +For example: +``` +$ cocli comid display --file data/comid/comid-dice-refval.cbor +``` +provided the `comid-dice-refval.cbor` file contains valid CoMID, would print something like: +``` +>> [comid-dice-refval.cbor] +{ + "tag-identity": { + "id": "1d5a8c7c-1c70-4c56-937e-3c5713ae5a83" + }, + "triples": {} +[...] +} +``` +While a `data/comid/` folder with the following contents: +``` +$ tree data/comid/ +data/comid/ +├── rubbish.cbor +├── 1.cbor +└── 2.cbor +``` +could be inspected in one go using: +``` +$ cocli comid display --dir data/comid/ +``` +which would output something like: +``` +>> failed displaying "comids.d/rubbish.cbor": CBOR decoding failed: EOF +>> [data/comid/1.cbor] +{ + "tag-identity": { + "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" + }, +[...] +} +>> [data/comid/2.cbor] +{ + "tag-identity": { + "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" + }, +[...] +} +Error: 1/3 display(s) failed +``` + +One or more files and directories can be supplied in the same invocation, e.g.: +``` +$ cocli comid display -f m1.cbor \ + -f comids.d/m2.cbor \ + -d /var/spool/comids \ + -d yet-another-comid-folder/ +``` ## CoTSs manipulation -The instructions to manipulate CoTSs are documented [here](COTS.md) +The `cots` subcommand allows you to create, display and validate CoTSs. + +### Create + +Use the `cots create` subcommand to create a CBOR-encoded CoTS. The `environment` switch takes in a JSON template specifiying the environments that are valid for the keys specified and the `tas` switch takes in a directory of trust anchors files: + +* Please inspect `data/cots/templates` JSON templates as examples for `environment` and `claims` + + +``` +$ cocli cots create --environment data/cots/env/vendor.json --tafile data/cots/shared_ta.ta +``` +On success, you should see something like the following printed to stdout: +``` +>> created "vendor.cbor" +``` + +The CBOR-encoded CoTS file is stored in the current working directory with a +name derived from its environment template. If you want, you can specify a different +target directory and file name using the `--output` command line switch (abbrev. `-o`) +``` +$ cocli cots create --environment data/cots/env/vendor.json --tafile data/cots/shared_ta.ta --output /tmp/myCots.cbor +>> created "/tmp/myCots.cbor" +``` +Note that the output directory, as well as all its parent directories, MUST pre-exist. + +### Display + +Use the `cots display` subcommand to print to stdout one or more CBOR-encoded +CoTSs in human readable (JSON) format. + +You can supply individual files using the `--file` switch (abbrev. `-f`), or +directories that may (or may not) contain CoTS files using the `--dir` switch +(abbrev. `-d`). Only valid CoTSs will be displayed, and any decoding or +validation error will be printed alongside the corresponding file name. + +For example: +``` +$ cocli cots display --file vendor.cbor +``` +provided the `vendor.cbor` file contains valid CoTS, would print something like: +``` +>> [vendor.cbor] +{ + "environments": [ + { + "environment": { + "class": { + "vendor": "Zesty Hands, Inc." + } + } + } + ], + "keys": { + "tas": [ + { + "format": 1, + "data": "ooICejCCAnYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATjUaoQOSQHpL0DfKC8EVTQ5wHwZ085yyxPkhBpLOu+7B0nl33FYWV1Hg4je/37FTbpmohFkUKWYd81z8C/K1DMBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzCCAgEwPjELMAkGA1UEBgwCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHTAbBgNVBAMMFEV4YW1wbGUgVHJ1c3QgQW5jaG9yoIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M=" + } + ] + } +} + +``` +While a `data/cots` folder with the following contents: +``` +$ tree cots/ +cots/ +├── rubbish.cbor +├── namedtastore.cbor +├── vendor.cbor +``` +could be inspected in one go using: +``` +$ cocli cots display --dir data/cots/ +``` +which would output something like: +``` +>> [data/cots/namedtastore.cbor] +{ + "environments": [ + { + "namedtastore": "Miscellaneous TA Store" + } + ], + "keys": { + "tas": [ + { + "format": 1, + "data": "ooIC1TCCAtEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN0f5kzywEzZOYbaV23O3N8cku39JoLNjlHPwECbXDDWp0LpAO1z248/hoy6UW/TZMTPPR/93XwHsG16mSFy8XBBSKhM/5gJWjvDbW7qUY1peNm9cfYDCCAlwwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yoIIB+jCCAZ+gAwIBAgIUEBuTRGXAEEVEHhu4xafAnqm+qYgwCgYIKoZIzj0EAwIwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMB4XDTIyMDUxOTE1MTMwOFoXDTMyMDUxNjE1MTMwOFowXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzdH+ZM8sBM2TmG2ldtztzfHJLt/SaCzY5Rz8BAm1ww1qdC6QDtc9uPP4aMulFv02TEzz0f/d18B7BtepkhcvF6M/MD0wHQYDVR0OBBYEFIqEz/mAlaO8NtbupRjWl42b1x9gMAsGA1UdDwQEAwIChDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQC2cf43f3PPlCO6/dxv40ftIgxxToKHF72UzENv7+y4ygIhAIGtC/r6SGaFMaP7zD2EloBuIXTtyWu8Hwl+YGdXRY93" + } + ] + } +} +>> failed displaying "data/cots/rubbish.cbor": CBOR decoding failed: cbor: cannot unmarshal primitives into Go value of type cots.ConciseTaStore +>> [data/cots/vendor.cbor] +{ + "environments": [ + { + "environment": { + "class": { + "vendor": "Zesty Hands, Inc." + } + } + } + ], + "keys": { + "tas": [ + { + "format": 1, + "data": "ooICejCCAnYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATjUaoQOSQHpL0DfKC8EVTQ5wHwZ085yyxPkhBpLOu+7B0nl33FYWV1Hg4je/37FTbpmohFkUKWYd81z8C/K1DMBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzCCAgEwPjELMAkGA1UEBgwCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHTAbBgNVBAMMFEV4YW1wbGUgVHJ1c3QgQW5jaG9yoIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M=" + } + ] + } +} + +Note: One of more files and directories can be supplied in the same invocation, using -f and -d directive: + +``` ## CoSWID manipulation + Tooling to manipulate `CoSWID` is not currently available under Project Veraison. However CoSWID can be part of CoRIM by constructing CoSWID CBOR by other indistry available tools such as [swid-tools](https://github.com/usnistgov/swid-tools) and including them as mentioned under [CORIM Construction](CORIM.md) ## CoRIMs manipulation -The instructions to manipulate CoRIMs are documented [here](CORIM.md) + +The `corim` subcommand allows you to create, display, sign, verify CoRIMs or submit +a CoRIM using the [Veraison provisioning API](https://github.com/veraison/docs/tree/main/api/endorsement-provisioning). +It also provides a means to extract as-is the embedded CoSWIDs, CoMIDs and CoTSs and save +them as separate files. + +### Create + +Use the `corim create` subcommand to create a CBOR-encoded, unsigned CoRIM, by +passing its JSON representation via the `--template` switch (or equivalently its `-t` shorthand) +together with the CBOR-encoded CoMIDs, CoSWIDs and/or CoTS to be embedded. + +* Please inspect `corim` JSON templates as examples under `data/corim/templates` `corim-*.json` + +``` +$ cocli corim create --template data/corim/templates/corim-full.json --comid data/comid/comid-dice-refval.cbor --coswid data/coswid/1.cbor --cots data/cots/vendor.cbor +``` +On success, you should see something like the following printed to stdout: +``` +>> created "corim-full.cbor" from "corim-full.json" +``` + +The CBOR-encoded CoRIM file is stored in the current working directory with a +name derived from its template. If you want, you can specify a different +file name using the `--output` command line switch (abbrev. `-o`): +``` +$ cocli corim create -t data/corim/templates/corim-full.json -m data/comid/comid-dice-refval.cbor -s data/coswid/1.cbor -c data/cots/c1.cbor -o unsigned-corim.cbor +>> created "unsigned-corim.cbor" from "corim-full.json" +``` + +CoMIDs, CoSWIDs and CoTSs can be either supplied as individual files, using the +`--comid` (abbrev. `-m`), `--coswid` (abbrev. `-s`) and `--cots` (abbrev. `-c`) switches respectively, or +as "per-folder" blocks using the `--comid-dir` (abbrev. `-M`), `--coswid-dir` and `--cots-dir` +(abbrev. `-C`) switch. For example: +``` +$ cocli corim create --template data/corim/templates/corim-full.json --comid-dir data/comid/cbor/ +``` + +Creation will fail if *any* of the inputs is non conformant. For example, if +`data/comid/cbor/` contains an invalid CoMID file `rubbish.cbor`, an attempt to create a +CoRIM: +``` +$ cocli corim create -t data/corim/templates/corim-full.json -M data/comid/cbor/ +``` +will fail with: +``` +Error: error loading CoMID from data/comid/cbor/rubbish.cbor: EOF +``` + +### Sign + +Use the `corim sign` subcommand to cryptographically seal the unsigned CoRIM +supplied via the `--file` switch (abbrev. `-f`). The signature is produced +using the key supplied via the `--key` switch (abbrev. `-k`), which is expected +to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. On success, the +resulting COSE Sign1 payload is saved to file whose name can be controlled using +the `--output` switch (abbrev. `-o`). A CoRIM Meta template in JSON format must +also be provided using the `--meta` switch (abbrev.`-m`). + +* Please inspect the `data/corim/templates` directory for `meta` JSON templates. + +For example, with the default output file: +``` +$ cocli corim sign --file corim.cbor --key ec-p256.jwk --meta meta.json +>> "corim.cbor" signed and saved to "signed-corim.cbor" +``` +Or, the same but with a custom output file: +``` +$ cocli corim sign --file data/corim/corim-full.cbor \ + --key data/keys/ec-p256.jwk \ + --meta data/corim/templates/meta-full.json \ + --output /var/spool/signed-corim.cbor +>> "corim-full.cbor" signed and saved to "/var/spool/signed-corim.cbor" +``` + +### Verify + +Use the `corim verify` subcommand to cryptographically verify the signed CoRIM +supplied via the `--file` switch (abbrev. `-f`). The signature is checked +using the key supplied via the `--key` switch (abbrev. `-k`), which is expected +to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. For example: +``` +$ cocli corim verify --file data/corim/signed-corim.cbor --key data/keys/ec-p256.jwk +>> "signed-corim.cbor" verified +``` + +Verification can fail either because the cryptographic processing fails or +because the signed payload or protected headers are themselves invalid. For example: +``` +$ cocli corim verify --file data/corim/signed-corim-bad-signature.cbor --key data/keys/ec-p256.jwk +``` +will give +``` +Error: error verifying signed-corim-bad-signature.cbor with key ec-p256.jwk: verification failed ecdsa.Verify +``` + +### Display + +Use the `corim display` subcommand to print to stdout a signed CoRIM in human +readable (JSON) format. + +You must supply the file you want to display using the `--file` switch (abbrev. +`-f`). Only a valid CoRIM will be displayed, and any occurring decoding or +validation errors will be printed instead. + +The output has two logical sections: one for Meta and one for the (unsigned) +CoRIM: +``` +$ cocli corim display --file data/corim/signed-corim.cbor +Meta: +{ + "signer": { + "name": "ACME Ltd signing key", + "uri": "https://acme.example/signing-key.pub" + }, +[...] +} +Corim: +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "tags": [ + "2QH...", +[...] + ] +} +``` + +By default, the embedded CoMID, CoSWID and CoTS tags are not expanded, and what you +will see is the base64 encoding of their CBOR serialisation. If you want to +peek at the tags' content, supply the `--show-tags` (abbrev. `-v`) switch, which +will add a further Tags section with one entry per each expanded tag: +``` +$ cocli corim display --file data/corim/signed-corim.cbor --show-tags +Meta: +{ +[...] +} +Corim: +{ +[...] +} +Tags: +>> [ 0 ] +{ + "tag-identity": { + "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" + }, +[...] +} +>> [ 1 ] +{ + "tag-identity": { + "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" + }, +[...] +} +>> [ 2 ] +{ + "tag-id": "com.acme.rrd2013-ce-sp1-v4-1-5-0", +[...] +} +``` + +### Extract CoSWIDs, CoMIDs and CoTSs + +Use the `corim extract` subcommand to extract the embedded CoMIDs, CoSWIDs and CoTSs +from a signed CoRIM. + +You must supply a signed CoRIM file using the `--file` switch (abbrev. `-f`) and +an optional output folder (default is the current working directory) using the +`--output-dir` switch (abbrev. `-o`). Make sure that the output directory as +well as any parent folder exists prior to issuing the command. + +On success, the found CoMIDs, CoSWIDs, CoTS are saved in CBOR format: +``` +$ cocli corim extract --file data/corim/signed-corim.cbor --output-dir output.d/ +$ tree output.d/ +output.d/ +├── 000000-comid.cbor +├── 000001-comid.cbor +├── 000002-coswid.cbor +└── 000003-cots.cbor +``` ## CoRIM Submission to Veraison From fcdecbef9ad7346b0384ae689993747b9b694674 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 11 Jan 2024 10:07:14 -0500 Subject: [PATCH 025/110] Tidies up! Signed-off-by: Yogesh Deshpande --- cocli/COMID.md | 129 ---------------------------------- cocli/CORIM.md | 184 ------------------------------------------------- cocli/COTS.md | 122 -------------------------------- 3 files changed, 435 deletions(-) delete mode 100644 cocli/COMID.md delete mode 100644 cocli/CORIM.md delete mode 100644 cocli/COTS.md diff --git a/cocli/COMID.md b/cocli/COMID.md deleted file mode 100644 index 15b4f8c9..00000000 --- a/cocli/COMID.md +++ /dev/null @@ -1,129 +0,0 @@ - -# CoMIDs manipulation - -The `comid` subcommand allows you to create, display and validate CoMIDs. - -## Create - -Use the `comid create` subcommand to create a CBOR-encoded CoMID, passing its -JSON representation via the `--template` switch (or equivalently its `-t` shorthand): - -* Please inspect `comid` JSON templates as examples under `data/comid/templates` `comid-*.json` - -``` -$ cocli comid create --template data/comid/templates/comid-dice-refval.json -``` -On success, you should see something like the following printed to stdout: -``` ->> created "comid-dice-refval.cbor" from "comid-dice-refval.json" -``` - -The CBOR-encoded CoMID file is stored in the current working directory with a -name derived from its template. If you want, you can specify a different -target directory using the `--output-dir` command line switch (abbrev. `-o`) -``` -$ cocli comid create --template data/comid/templates/comid-dice-refval.json --output-dir /tmp ->> created "/tmp/comid-dice-refval.cbor" from "comid-dice-refval.json" -``` -Note that the output directory, as well as all its parent directories, MUST -pre-exist. - -You can also create multiple CoMIDs in one go. Suppose all your templates are -stored in the `templates/` folder: -``` -$ tree templates/ -templates/ -├── comid-dice-refval1.json -├── comid-dice-refval2.json -... -└── comid-dice-refvaln.json -``` -Then, you can use the `--template-dir` (abbrev. `-T`), and let the tool load, -validate, and CBOR-encode the templates one by one: -``` -$ cocli comid create --template-dir templates ->> created "comid-dice-refval1.cbor" from "templates/comid-dice-refval1.json" ->> created "comid-dice-refval2.cbor" from "templates/comid-dice-refval2.json" -... ->> created "comid-dice-refvaln.cbor" from "templates/comid-dice-refvaln.json" -``` - -You can specify both the `-T` and `-t` switches as many times as needed, and -even combine them in one invocation: -``` -$ cocli comid create -T comid-templates/ \ - -T comid-templates-aux/ \ - -t extra-comid.json \ - -t yet-another-comid.json \ - -o /var/spool/comid -``` - -**NOTE** that since the output file name is deterministically generated from the -template file name, all the template files (when from different directories) -MUST have different base names. - - -## Display - -Use the `comid display` subcommand to print to stdout one or more CBOR-encoded -CoMIDs in human readable (JSON) format. - -You can supply individual files using the `--file` switch (abbrev. `-f`), or -directories that may (or may not) contain CoMID files using the `--dir` switch -(abbrev. `-d`). Only valid CoMIDs will be displayed, and any decoding or -validation error will be printed alongside the corresponding file name. - -For example: -``` -$ cocli comid display --file data/comid/comid-dice-refval.cbor -``` -provided the `comid-dice-refval.cbor` file contains valid CoMID, would print something like: -``` ->> [comid-dice-refval.cbor] -{ - "tag-identity": { - "id": "1d5a8c7c-1c70-4c56-937e-3c5713ae5a83" - }, - "triples": {} -[...] -} -``` -While a `data/comid/` folder with the following contents: -``` -$ tree data/comid/ -data/comid/ -├── rubbish.cbor -├── 1.cbor -└── 2.cbor -``` -could be inspected in one go using: -``` -$ cocli comid display --dir data/comid/ -``` -which would output something like: -``` ->> failed displaying "comids.d/rubbish.cbor": CBOR decoding failed: EOF ->> [data/comid/1.cbor] -{ - "tag-identity": { - "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" - }, -[...] -} ->> [data/comid/2.cbor] -{ - "tag-identity": { - "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" - }, -[...] -} -Error: 1/3 display(s) failed -``` - -One or more files and directories can be supplied in the same invocation, e.g.: -``` -$ cocli comid display -f m1.cbor \ - -f comids.d/m2.cbor \ - -d /var/spool/comids \ - -d yet-another-comid-folder/ -``` \ No newline at end of file diff --git a/cocli/CORIM.md b/cocli/CORIM.md deleted file mode 100644 index fdd40255..00000000 --- a/cocli/CORIM.md +++ /dev/null @@ -1,184 +0,0 @@ -# CoRIMs manipulation - -The `corim` subcommand allows you to create, display, sign, verify CoRIMs or submit -a CoRIM using the [Veraison provisioning API](https://github.com/veraison/docs/tree/main/api/endorsement-provisioning). -It also provides a means to extract as-is the embedded CoSWIDs, CoMIDs and CoTSs and save -them as separate files. - -## Create - -Use the `corim create` subcommand to create a CBOR-encoded, unsigned CoRIM, by -passing its JSON representation via the `--template` switch (or equivalently its `-t` shorthand) -together with the CBOR-encoded CoMIDs, CoSWIDs and/or CoTS to be embedded. - -* Please inspect `corim` JSON templates as examples under `data/corim/templates` `corim-*.json` - -``` -$ cocli corim create --template data/corim/templates/corim-full.json --comid data/comid/comid-dice-refval.cbor --coswid data/coswid/1.cbor --cots data/cots/vendor.cbor -``` -On success, you should see something like the following printed to stdout: -``` ->> created "corim-full.cbor" from "corim-full.json" -``` - -The CBOR-encoded CoRIM file is stored in the current working directory with a -name derived from its template. If you want, you can specify a different -file name using the `--output` command line switch (abbrev. `-o`): -``` -$ cocli corim create -t data/corim/templates/corim-full.json -m data/comid/comid-dice-refval.cbor -s data/coswid/1.cbor -c data/cots/c1.cbor -o unsigned-corim.cbor ->> created "unsigned-corim.cbor" from "corim-full.json" -``` - -CoMIDs, CoSWIDs and CoTSs can be either supplied as individual files, using the -`--comid` (abbrev. `-m`), `--coswid` (abbrev. `-s`) and `--cots` (abbrev. `-c`) switches respectively, or -as "per-folder" blocks using the `--comid-dir` (abbrev. `-M`), `--coswid-dir` and `--cots-dir` -(abbrev. `-C`) switch. For example: -``` -$ cocli corim create --template data/corim/templates/corim-full.json --comid-dir data/comid/cbor/ -``` - -Creation will fail if *any* of the inputs is non conformant. For example, if -`data/comid/cbor/` contains an invalid CoMID file `rubbish.cbor`, an attempt to create a -CoRIM: -``` -$ cocli corim create -t data/corim/templates/corim-full.json -M data/comid/cbor/ -``` -will fail with: -``` -Error: error loading CoMID from data/comid/cbor/rubbish.cbor: EOF -``` - -## Sign - -Use the `corim sign` subcommand to cryptographically seal the unsigned CoRIM -supplied via the `--file` switch (abbrev. `-f`). The signature is produced -using the key supplied via the `--key` switch (abbrev. `-k`), which is expected -to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. On success, the -resulting COSE Sign1 payload is saved to file whose name can be controlled using -the `--output` switch (abbrev. `-o`). A CoRIM Meta template in JSON format must -also be provided using the `--meta` switch (abbrev.`-m`). - -* Please inspect the `data/corim/templates` directory for `meta` JSON templates. - -For example, with the default output file: -``` -$ cocli corim sign --file corim.cbor --key ec-p256.jwk --meta meta.json ->> "corim.cbor" signed and saved to "signed-corim.cbor" -``` -Or, the same but with a custom output file: -``` -$ cocli corim sign --file data/corim/corim-full.cbor \ - --key data/keys/ec-p256.jwk \ - --meta data/corim/templates/meta-full.json \ - --output /var/spool/signed-corim.cbor ->> "corim-full.cbor" signed and saved to "/var/spool/signed-corim.cbor" -``` - -## Verify - -Use the `corim verify` subcommand to cryptographically verify the signed CoRIM -supplied via the `--file` switch (abbrev. `-f`). The signature is checked -using the key supplied via the `--key` switch (abbrev. `-k`), which is expected -to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. For example: -``` -$ cocli corim verify --file data/corim/signed-corim.cbor --key data/keys/ec-p256.jwk ->> "signed-corim.cbor" verified -``` - -Verification can fail either because the cryptographic processing fails or -because the signed payload or protected headers are themselves invalid. For example: -``` -$ cocli corim verify --file data/corim/signed-corim-bad-signature.cbor --key data/keys/ec-p256.jwk -``` -will give -``` -Error: error verifying signed-corim-bad-signature.cbor with key ec-p256.jwk: verification failed ecdsa.Verify -``` - -## Display - -Use the `corim display` subcommand to print to stdout a signed CoRIM in human -readable (JSON) format. - -You must supply the file you want to display using the `--file` switch (abbrev. -`-f`). Only a valid CoRIM will be displayed, and any occurring decoding or -validation errors will be printed instead. - -The output has two logical sections: one for Meta and one for the (unsigned) -CoRIM: -``` -$ cocli corim display --file data/corim/signed-corim.cbor -Meta: -{ - "signer": { - "name": "ACME Ltd signing key", - "uri": "https://acme.example/signing-key.pub" - }, -[...] -} -Corim: -{ - "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", - "tags": [ - "2QH...", -[...] - ] -} -``` - -By default, the embedded CoMID, CoSWID and CoTS tags are not expanded, and what you -will see is the base64 encoding of their CBOR serialisation. If you want to -peek at the tags' content, supply the `--show-tags` (abbrev. `-v`) switch, which -will add a further Tags section with one entry per each expanded tag: -``` -$ cocli corim display --file data/corim/signed-corim.cbor --show-tags -Meta: -{ -[...] -} -Corim: -{ -[...] -} -Tags: ->> [ 0 ] -{ - "tag-identity": { - "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" - }, -[...] -} ->> [ 1 ] -{ - "tag-identity": { - "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" - }, -[...] -} ->> [ 2 ] -{ - "tag-id": "com.acme.rrd2013-ce-sp1-v4-1-5-0", -[...] -} -``` - -## Extract CoSWIDs, CoMIDs and CoTSs - -Use the `corim extract` subcommand to extract the embedded CoMIDs, CoSWIDs and CoTSs -from a signed CoRIM. - -You must supply a signed CoRIM file using the `--file` switch (abbrev. `-f`) and -an optional output folder (default is the current working directory) using the -`--output-dir` switch (abbrev. `-o`). Make sure that the output directory as -well as any parent folder exists prior to issuing the command. - -On success, the found CoMIDs, CoSWIDs, CoTS are saved in CBOR format: -``` -$ cocli corim extract --file data/corim/signed-corim.cbor --output-dir output.d/ -$ tree output.d/ -output.d/ -├── 000000-comid.cbor -├── 000001-comid.cbor -├── 000002-coswid.cbor -└── 000003-cots.cbor -``` \ No newline at end of file diff --git a/cocli/COTS.md b/cocli/COTS.md deleted file mode 100644 index fc094820..00000000 --- a/cocli/COTS.md +++ /dev/null @@ -1,122 +0,0 @@ - -## CoTSs manipulation - -The `cots` subcommand allows you to create, display and validate CoTSs. - -### Create - -Use the `cots create` subcommand to create a CBOR-encoded CoTS. The `environment` switch takes in a JSON template specifiying the environments that are valid for the keys specified and the `tas` switch takes in a directory of trust anchors files: - -* Please inspect `data/cots/templates` JSON templates as examples for `environment` and `claims` - - -``` -$ cocli cots create --environment data/cots/env/vendor.json --tafile data/cots/shared_ta.ta -``` -On success, you should see something like the following printed to stdout: -``` ->> created "vendor.cbor" -``` - -The CBOR-encoded CoTS file is stored in the current working directory with a -name derived from its environment template. If you want, you can specify a different -target directory and file name using the `--output` command line switch (abbrev. `-o`) -``` -$ cocli cots create --environment data/cots/env/vendor.json --tafile data/cots/shared_ta.ta --output /tmp/myCots.cbor ->> created "/tmp/myCots.cbor" -``` -Note that the output directory, as well as all its parent directories, MUST pre-exist. - -### Display - -Use the `cots display` subcommand to print to stdout one or more CBOR-encoded -CoTSs in human readable (JSON) format. - -You can supply individual files using the `--file` switch (abbrev. `-f`), or -directories that may (or may not) contain CoTS files using the `--dir` switch -(abbrev. `-d`). Only valid CoTSs will be displayed, and any decoding or -validation error will be printed alongside the corresponding file name. - -For example: -``` -$ cocli cots display --file vendor.cbor -``` -provided the `vendor.cbor` file contains valid CoTS, would print something like: -``` ->> [vendor.cbor] -{ - "environments": [ - { - "environment": { - "class": { - "vendor": "Zesty Hands, Inc." - } - } - } - ], - "keys": { - "tas": [ - { - "format": 1, - "data": "ooICejCCAnYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATjUaoQOSQHpL0DfKC8EVTQ5wHwZ085yyxPkhBpLOu+7B0nl33FYWV1Hg4je/37FTbpmohFkUKWYd81z8C/K1DMBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzCCAgEwPjELMAkGA1UEBgwCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHTAbBgNVBAMMFEV4YW1wbGUgVHJ1c3QgQW5jaG9yoIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M=" - } - ] - } -} - -``` -While a `data/cots` folder with the following contents: -``` -$ tree cots/ -cots/ -├── rubbish.cbor -├── namedtastore.cbor -├── vendor.cbor -``` -could be inspected in one go using: -``` -$ cocli cots display --dir data/cots/ -``` -which would output something like: -``` ->> [data/cots/namedtastore.cbor] -{ - "environments": [ - { - "namedtastore": "Miscellaneous TA Store" - } - ], - "keys": { - "tas": [ - { - "format": 1, - "data": "ooIC1TCCAtEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN0f5kzywEzZOYbaV23O3N8cku39JoLNjlHPwECbXDDWp0LpAO1z248/hoy6UW/TZMTPPR/93XwHsG16mSFy8XBBSKhM/5gJWjvDbW7qUY1peNm9cfYDCCAlwwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yoIIB+jCCAZ+gAwIBAgIUEBuTRGXAEEVEHhu4xafAnqm+qYgwCgYIKoZIzj0EAwIwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMB4XDTIyMDUxOTE1MTMwOFoXDTMyMDUxNjE1MTMwOFowXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzdH+ZM8sBM2TmG2ldtztzfHJLt/SaCzY5Rz8BAm1ww1qdC6QDtc9uPP4aMulFv02TEzz0f/d18B7BtepkhcvF6M/MD0wHQYDVR0OBBYEFIqEz/mAlaO8NtbupRjWl42b1x9gMAsGA1UdDwQEAwIChDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQC2cf43f3PPlCO6/dxv40ftIgxxToKHF72UzENv7+y4ygIhAIGtC/r6SGaFMaP7zD2EloBuIXTtyWu8Hwl+YGdXRY93" - } - ] - } -} ->> failed displaying "data/cots/rubbish.cbor": CBOR decoding failed: cbor: cannot unmarshal primitives into Go value of type cots.ConciseTaStore ->> [data/cots/vendor.cbor] -{ - "environments": [ - { - "environment": { - "class": { - "vendor": "Zesty Hands, Inc." - } - } - } - ], - "keys": { - "tas": [ - { - "format": 1, - "data": "ooICejCCAnYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATjUaoQOSQHpL0DfKC8EVTQ5wHwZ085yyxPkhBpLOu+7B0nl33FYWV1Hg4je/37FTbpmohFkUKWYd81z8C/K1DMBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzCCAgEwPjELMAkGA1UEBgwCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHTAbBgNVBAMMFEV4YW1wbGUgVHJ1c3QgQW5jaG9yoIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M=" - } - ] - } -} - -Note: One of more files and directories can be supplied in the same invocation, using -f and -d directive: - -``` From 10354cf2605d7740081b1d079bb65b78e6bf6ddc Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 11 Jan 2024 11:13:37 -0500 Subject: [PATCH 026/110] Add a ToC Signed-off-by: Yogesh Deshpande --- cocli/README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/cocli/README.md b/cocli/README.md index 09776d66..0ec8c012 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -1,6 +1,27 @@ # Corim Command Line Interface -## Installing and configuring +In this document we describe how to use Corim Command Line Interface tool `cocli` + +* [Installation](#installing-and-configuring) +* [Command Snapshot](#cocli-command-snapshot) +* [Supported Commands](#supported-commands) + * [CoMID Commands](#comids-manipulation) + * [Create](#create) + * [Display](#display) + * [CoTS Commands](#cotss-manipulation) + * [Create](#create-1) + * [Display](#display-1) + * [CoRIM Commands](#corims-manipulation) + * [Create](#create-2) + * [Sign](#sign) + * [Verify](#verify) + * [Display](#display-2) + * [Extract](#extract-coswids-comids-and-cotss) + * [CoRIM Submission](#corim-submission-to-veraison) + * [Remote Authentication](#remote-service-authentication) + * [Command Synopsis](#visual-synopsis-of-the-available-commands) + +# Installing and configuring To install the `cocli` command, do: ``` @@ -67,6 +88,9 @@ end ``` +# Supported Commands +This section describes all the available commands supported by `cocli` tool + ## CoMIDs manipulation The `comid` subcommand allows you to create, display and validate CoMIDs. From 7346232f44cf08bc78fb0611a557f6f108d63549 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Fri, 12 Jan 2024 07:16:13 -0500 Subject: [PATCH 027/110] Align CBOR payloads to latest of CoRIM and CoMID packages Signed-off-by: Yogesh Deshpande --- cocli/data/comid/comid-cca-mult-refval.cbor | Bin 0 -> 457 bytes cocli/data/comid/comid-cca-refval.cbor | Bin 0 -> 177 bytes cocli/data/comid/comid-dice-refval.cbor | Bin 224 -> 220 bytes cocli/data/comid/comid-psa-iakpub.cbor | Bin 0 -> 637 bytes cocli/data/comid/comid-psa-integ-iakpub.cbor | Bin 0 -> 638 bytes cocli/data/comid/comid-psa-refval.cbor | Bin 0 -> 416 bytes cocli/data/corim/corim-full.cbor | Bin 436 -> 1495 bytes cocli/data/corim/signed-corim-cbor | Bin 0 -> 1622 bytes cocli/data/corim/unsigned-corim.cbor | Bin 436 -> 815 bytes 9 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 cocli/data/comid/comid-cca-mult-refval.cbor create mode 100644 cocli/data/comid/comid-cca-refval.cbor create mode 100644 cocli/data/comid/comid-psa-iakpub.cbor create mode 100644 cocli/data/comid/comid-psa-integ-iakpub.cbor create mode 100644 cocli/data/comid/comid-psa-refval.cbor create mode 100644 cocli/data/corim/signed-corim-cbor diff --git a/cocli/data/comid/comid-cca-mult-refval.cbor b/cocli/data/comid/comid-cca-mult-refval.cbor new file mode 100644 index 0000000000000000000000000000000000000000..12761868beeed4160df8c0ff3fb7b1b0adf5fd0c GIT binary patch literal 457 zcmZ3&keZsO>+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<=fs{GbvD_5=T#Jbe|lrri)_Y)OpQ&95en^2{YRA=Yz^7ss?XVcuzym@ znP&L(LG|7${(A{W&Z{1Wnw=65lpn&9YN%(dXG((UOvz0%JGcFv>R+^Lz5X%pLytbl zX6pJr*KB*$(4RN6(H>^HBiM8UpxaGIFn#gT=b|6ZgnJbq{<&{=_91_x|5vwFTNJq* sI>~vfe3nQB$n>bJ+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<=KLASl2U-9C delta 25 ecmcb^_<(VOE#u;ecG^tLToW^ufy`ciAQJ#?M+bHQ diff --git a/cocli/data/comid/comid-psa-iakpub.cbor b/cocli/data/comid/comid-psa-iakpub.cbor new file mode 100644 index 0000000000000000000000000000000000000000..4975373f7cfcaf72e8c4b4eb0f349ac6f7d67862 GIT binary patch literal 637 zcmZ3&keZsO>+Zz3kRiYJ;oafB^f0p z1;tkS`iaT8sd}juiMa(isZ7lbj7%&GnVOmwF)U`d$rKTx0Fu_t1WD$m=9MItWaj7T zW~S&GK!G7+3dmHZtf2hFl%UeQywoDbn@nmEij3!8udD7K{S*R1oqRl<6}(*|!Gc`AZrSCo<&hrV`B9!# zSq9FIfsvk2wu@t6vP)pPqpMqlS@T@n0cPIe`;!~L8fP6cDS*JZ&Zk}Ye;5ZrdLY3 ztt}VW39f!F=&qtd!1yeF@|F9O$KmPiOuQNYFT`||l?U!ub3HZ5K73{On@iKE5-?_A z<%Ox{8I{KV7Lf&s+D@UaAwHp5PJW@rMXnKHp_L_;5gtM29$w)-?&c98-hnBWnTFY6 nK@q7=0WLmnd1;YFrH1;cr4>PWUcTlYmZAA>E+Zz3kRiYJ;oafB^f0p z1;tkS`iaT8sd}juiMa(isZ7lbj7%&GnVOmwF)U`d$rKTx0Fu_t1WD$m=9MItWaj7T zW~S&GK!G7+3dmHZtf2hFl%UeQywoDbn@nmEij3!8udD7K{S*R1oqRl<6}(*|!Gc`AZrSCo<&hrV`B9!# zSq9FIfsvk2wu@t6vP)pPqpPpClXHllXR>!_uuEp9pX)HTE>G|R~^)VRnsA}qAB#4^Go$lSv#+{fKKBE&l| t#WK?{J1i(7)hWQm$1N`{vZ&NhKee2&IJGf literal 0 HcmV?d00001 diff --git a/cocli/data/comid/comid-psa-refval.cbor b/cocli/data/comid/comid-psa-refval.cbor new file mode 100644 index 0000000000000000000000000000000000000000..acd824043708af039e2074805c5e0e597566ba72 GIT binary patch literal 416 zcmZ3&keZsO>+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<=axr6)lMhR(k)ENR0c(W9n%#oOpWIR` zVhyr+a!yJm>fs{GbvD_5=T#Jbe|lrri)_Y)OpQ&95en^2{YRA=Yz^7ss?XVcuzym@ znP&L(LG|7${(A{W&Z{1Wnw=65lpn&9YN%(dXG((UOvz0%JGcFv>R+^Lz5X%pLytbl zX6pJr*KB*$(4RN6(H>^HBiM8UpxaGIFn#gT=b|6ZgnJbq{<&{=_91_x|5vwFTNJq* MI>~vfe3nQB0Gw#8o&W#< literal 0 HcmV?d00001 diff --git a/cocli/data/corim/corim-full.cbor b/cocli/data/corim/corim-full.cbor index 261316a478dd8340be49375f1f27c8554f4e9882..1f895aeb7ff6cd2a1b3011c16ecde85c1621cbc9 100644 GIT binary patch delta 1101 zcmdnOe4TrO2y=79{fXiZjEg2lXisca2GT42>mwO!ZZiH{!BC-)oS&StOxWAyZynu9u!yTCbO1l&+rxRG(LzTCAU5kfT>p zQ6iy{4i#csD6m*UA`2KKP>)JT=BgGH<);@V=B6Z;B&teCEffT*$U~^mORY#1kz(Lb zc){Mp7@@FO>@K@(G%L8O5&8@pDU$2nUjMm7#G zgPD=6vP@SO2Oy1(UV9sfxJnL4lcy^&R)UVAh#wN#Ez zx%%&KQM3A&vpQTSI!#NwZ+iZ~e(iuWEFz3Ct|!-QaMLP`y)Lkz;((~{@N54&qcVs1fBs-dibG)RP*M+7RO5K>fHT%zEZmza@yJO1)auBy*|m-cJF7~Nj! z&o``_)F_$9{c_pww_lkQ!W!-J1bHvnZSeSW&(7$z;DvuS9!5Pym1{ScUR8eeKX~G= FT>v9;MGMD=UnUiH%cL4z7Rtt;( diff --git a/cocli/data/corim/signed-corim-cbor b/cocli/data/corim/signed-corim-cbor new file mode 100644 index 0000000000000000000000000000000000000000..2b65e5abb1868547f4f61c98e45a71c93d9bcf32 GIT binary patch literal 1622 zcmcIke@qi+7{2eW6cA8|Fi#M@p`a12y(?{p$}jmPE})$twg}sf>roEqUAenTLFQ~F z>Y~o63vMoRw8bAWnSf0}gtUZ;k;e{~sbT8zEq{aEAg;Urm9u99~7O z9}L7EY{}P@U1gg3AAWG=)1$8ydn_q9=kFR$c*^(Wa@+mc=`Zhi|Dbwf3bybxTvI|K z;29kZdlb4&dE5Jv)@PCGSJw_^j^*5NhS!oee^}eoI_28y-=0hES1=VJgZ0iuLj!Y` zd`a!z);3Ud;Qk`ym!9sl4(#&VdwR1hplulZaS(E0G;h~Xw4Koif>lS5={U{cqJzZO z=y4J^;6x+@TtvXU6+C4XoE*mpSOFt3w8RUT@&}gBx9}2!6tm_2R$Kp+;JkhCP0CmQ-HHU;){DUU2Y2ps6TU7bsP(8GX?M zU>WBOikC6M8kT0nG;AqHYf!Aw-txDSCID8cd}xYmMssts#>P1{ykOJHlfrN!BWi7q zMvdf>(0R6CO6`sIqiDTsNbs2GbbGQx;B5kBw^9<7jK+IoWCZsNpgGWAC9u#>vK~p6oN-bD1 z#aV*~P|WyB-EZ_!jC1DX*=r{r#TiHTY%bi9*Fjx3oa;TF zYC5Be1yzNoKJG4>-&B1uy3^GgxAa$#BZ~qen~WgB)yPVfIuc%4Mn)5pm0(h2XrYU; zI~o}>kw{Q7NMvjymnx@4r7NK^7(O@u|JMY64v|G9%3dZ; zR=w~a9f?prPf^K6!R^EpC724p6IBR-$d7;eh%AAK0vL->&&^f$kDRe5mmDwH^!-Cn zbmt>--A?y(E@EW=qtS5~t8iy?F;N$?yNe%R%huhEIsa#7v2Lr-@=3R0X!e~aWw6@^ zUBXYXF z#Pr?dp^Wd22stMYPxNPw-`bKAY8?6Ot2y;`!{fXiZjEg2lXisca2GT42>mwO!ZZiH{!BC-)oS&StOxWAyZynu9u!yTCbO1l&+rxRG(LzTCAU5kfT>p zQ6iy{4i#csD6m*UA`2KKP>)JT=BgGH<);@V=B6Z;B&teCEffT*$U~^mORY#1kz(Lb xc){Mp7@@FO9;MGMD=UnUhPIb^!q12@A9U From 4c5ad70cab54fb66279e9ff7d1026cde24cae24e Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Mon, 15 Jan 2024 11:53:26 -0500 Subject: [PATCH 028/110] Tagged Bytes Implementation Fixes #105 Signed-off-by: Yogesh Deshpande --- comid/bytes.go | 50 ++++++++++++++++ comid/cbor.go | 2 +- comid/classid.go | 32 ++++++---- comid/classid_test.go | 122 ++++++++++++++++++++++++++++++++++++++ comid/group.go | 49 ++++++++++++++-- comid/group_test.go | 129 +++++++++++++++++++++++++++++++++++++++++ comid/instance.go | 22 +++++-- comid/instance_test.go | 123 +++++++++++++++++++++++++++++++++++++++ comid/rawvalue.go | 23 ++++---- comid/rawvalue_test.go | 13 ++++- comid/test_vars.go | 1 + 11 files changed, 533 insertions(+), 33 deletions(-) create mode 100644 comid/bytes.go diff --git a/comid/bytes.go b/comid/bytes.go new file mode 100644 index 00000000..61a66821 --- /dev/null +++ b/comid/bytes.go @@ -0,0 +1,50 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "fmt" +) + +const BytesType = "bytes" + +type TaggedBytes []byte + +func NewBytes(val any) (*TaggedBytes, error) { + var ret TaggedBytes + + if val == nil { + return &ret, nil + } + + switch t := val.(type) { + case string: + b := []byte(t) + ret = TaggedBytes(b) + case []byte: + ret = TaggedBytes(t) + case *[]byte: + ret = TaggedBytes(*t) + default: + return nil, fmt.Errorf("unexpected type for bytes: %T", t) + } + return &ret, nil +} + +func (o TaggedBytes) String() string { + return string(o) +} + +func (o TaggedBytes) Valid() error { + return nil +} + +func (o TaggedBytes) Type() string { + return "bytes" +} + +func (o TaggedBytes) Bytes() []byte { + + return o +} diff --git a/comid/cbor.go b/comid/cbor.go index ab8a9233..f4100783 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -29,7 +29,7 @@ var ( 557: TaggedThumbprint{}, 558: TaggedCOSEKey{}, 559: TaggedCertThumbprint{}, - 560: TaggedRawValueBytes{}, + 560: TaggedBytes{}, 561: TaggedCertPathThumbprint{}, // PSA profile tags 600: TaggedImplID{}, diff --git a/comid/classid.go b/comid/classid.go index acad47d5..706e1f8d 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -16,7 +16,7 @@ import ( ) // ClassID identifies the environment via a well-known identifier. This can be -// an OID, a UUID, or a profile-defined extension type. +// an OID, a UUID, a variable length opaque bytes or a profile-defined extension type. type ClassID struct { Value IClassIDValue } @@ -85,14 +85,15 @@ func (o *ClassID) UnmarshalCBOR(data []byte) error { // // where must be one of the known IClassIDValue implementation // type names (available in this implementation: "uuid", "oid", -// "psa.impl-id", "int"), and is the JSON encoding of the underlying +// "psa.impl-id", "int", "bytes"), and is the JSON encoding of the underlying // class id value. The exact encoding is dependent. For the base // implementation types it is // -// oid: dot-separated integers, e.g. "1.2.3.4" -// psa.impl-id: base64-encoded bytes, e.g. "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" -// uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" -// int: an integer value, e.g. 7 +// oid: dot-separated integers, e.g. "1.2.3.4" +// psa.impl-id: base64-encoded bytes, e.g. "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" +// uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" +// int: an integer value, e.g. 7 +// bytes: a variable length opaque bytes, example {0x07, 0x12, 0x34} func (o *ClassID) UnmarshalJSON(data []byte) error { var tnv encoding.TypeAndValue @@ -346,6 +347,17 @@ func (o TaggedInt) Bytes() []byte { return ret[:] } +// NewBytesClassID creates a New ClassID of type bytes +// The supplied interface parameter could be +// a byte slice, a pointer to a byte slice or a string +func NewBytesClassID(val any) (*ClassID, error) { + ret, err := NewBytes(val) + if err != nil { + return nil, err + } + return &ClassID{ret}, nil +} + // IClassIDFactory defines the signature for the factory functions that may be // registred using RegisterClassIDType to provide a new implementation of the // corresponding type choice. The factory function should create a new *ClassID @@ -357,10 +369,10 @@ func (o TaggedInt) Bytes() []byte { type IClassIDFactory func(any) (*ClassID, error) var classIDValueRegister = map[string]IClassIDFactory{ - OIDType: NewOIDClassID, - UUIDType: NewUUIDClassID, - IntType: NewIntClassID, - + OIDType: NewOIDClassID, + UUIDType: NewUUIDClassID, + IntType: NewIntClassID, + BytesType: NewBytesClassID, ImplIDType: NewImplIDClassID, } diff --git a/comid/classid_test.go b/comid/classid_test.go index aaeeb393..a9100031 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -441,3 +441,125 @@ func Test_RegisterClassIDType(t *testing.T) { require.NoError(t, err) assert.Equal(t, classID.Bytes(), out2.Bytes()) } + +func Test_NewBytesClassID_OK(t *testing.T) { + var testBytes = []byte{0x01, 0x02, 0x03, 0x04} + + for _, v := range []any{ + testBytes, + &testBytes, + string(testBytes), + } { + classID, err := NewBytesClassID(v) + require.NoError(t, err) + got := classID.Bytes() + assert.Equal(t, testBytes[:], got) + } +} + +func Test_NewBytesClassID_NOK(t *testing.T) { + for _, tv := range []struct { + Name string + Input any + Err string + }{ + + { + Name: "invalid input integer", + Input: 7, + Err: "unexpected type for bytes: int", + }, + { + Name: "invalid input fixed array", + Input: [3]byte{0x01, 0x02, 0x03}, + Err: "unexpected type for bytes: [3]uint8", + }, + } { + t.Run(tv.Name, func(t *testing.T) { + _, err := NewBytesClassID(tv.Input) + assert.EqualError(t, err, tv.Err) + }) + } +} + +func TestClassID_MarshalCBOR_Bytes(t *testing.T) { + tv, err := NewBytesClassID(TestBytes) + require.NoError(t, err) + // 560 (h'458999786556') + // tag(560): d9 0230 + expected := MustHexDecode(t, "d90230458999786556") + + actual, err := tv.MarshalCBOR() + fmt.Printf("CBOR: %x\n", actual) + + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +func TestClassID_UnmarshalCBOR_Bytes_OK(t *testing.T) { + tv := MustHexDecode(t, "d90230458999786556") + + var actual ClassID + err := actual.UnmarshalCBOR(tv) + + assert.Nil(t, err) + assert.Equal(t, "bytes", actual.Type()) + assert.Equal(t, TestBytes, actual.Bytes()) +} + +func TestClassID_MarshalJSONBytes_OK(t *testing.T) { + testBytes := []byte{0x01, 0x02, 0x03} + tb := TaggedBytes(testBytes) + tv := ClassID{&tb} + jsonBytes, err := tv.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, `{"type":"bytes","value":"AQID"}`, string(jsonBytes)) + +} + +func TestClassID_UnmarshalJSON_Bytes_OK(t *testing.T) { + for _, tv := range []struct { + Name string + Input string + }{ + { + Name: "valid input test 1", + Input: `{ "type": "bytes", "value": "MTIzNDU2Nzg5" }`, + }, + { + Name: "valid input test 2", + Input: `{ "type": "bytes", "value": "deadbeef"}`, + }, + } { + t.Run(tv.Name, func(t *testing.T) { + var actual ClassID + err := actual.UnmarshalJSON([]byte(tv.Input)) + require.NoError(t, err) + }) + } +} + +func TestClassID_UnmarshalJSON_Bytes_NOK(t *testing.T) { + for _, tv := range []struct { + Name string + Input string + Err string + }{ + { + Name: "invalid value", + Input: `{ "type": "bytes", "value": "/0" }`, + Err: "cannot unmarshal class id: illegal base64 data at input byte 0", + }, + { + Name: "invalid input", + Input: `{ "type": "bytes", "value": 10 }`, + Err: "cannot unmarshal class id: json: cannot unmarshal number into Go value of type comid.TaggedBytes", + }, + } { + t.Run(tv.Name, func(t *testing.T) { + var actual ClassID + err := actual.UnmarshalJSON([]byte(tv.Input)) + assert.EqualError(t, err, tv.Err) + }) + } +} diff --git a/comid/group.go b/comid/group.go index 789ed197..0678fb5e 100644 --- a/comid/group.go +++ b/comid/group.go @@ -12,7 +12,7 @@ import ( "github.com/veraison/corim/extensions" ) -// Group stores a group identity. The supported format is UUID. +// Group stores a group identity. The supported format is UUID and a variable length opaque bytes. type Group struct { Value IGroupValue } @@ -42,6 +42,23 @@ func (o Group) String() string { return o.Value.String() } +// Type returns the type of the Group +func (o Group) Type() string { + if o.Value == nil { + return "" + } + + return o.Value.Type() +} + +// Bytes returns a []byte containing the raw bytes of the group value +func (o Group) Bytes() []byte { + if o.Value == nil { + return []byte{} + } + return o.Value.Bytes() +} + // MarshalCBOR serializes the target group to CBOR func (o Group) MarshalCBOR() ([]byte, error) { return em.Marshal(o.Value) @@ -53,10 +70,18 @@ func (o *Group) UnmarshalCBOR(data []byte) error { } // UnmarshalJSON deserializes the supplied JSON type/value object into the Group -// target. The only supported format is UUID, e.g.: +// target. The following formats are supported: +// +// (a) UUID, e.g.: +// { +// "type": "uuid", +// "value": "69E027B2-7157-4758-BCB4-D9F167FE49EA" +// } +// +// (b) Tagged bytes, e.g. : // // { -// "type": "uuid", +// "type": "bytes", // "value": "69E027B2-7157-4758-BCB4-D9F167FE49EA" // } func (o *Group) UnmarshalJSON(data []byte) error { @@ -93,6 +118,8 @@ func (o Group) MarshalJSON() ([]byte, error) { type IGroupValue interface { extensions.ITypeChoiceValue + + Bytes() []byte } func NewUUIDGroup(val any) (*Group, error) { @@ -117,8 +144,19 @@ func MustNewUUIDGroup(val any) *Group { return ret } +// NewBytesGroup creates a New Group of type bytes +// The supplied interface parameter could be +// a byte slice, a pointer to a byte slice or a string +func NewBytesGroup(val any) (*Group, error) { + ret, err := NewBytes(val) + if err != nil { + return nil, err + } + return &Group{ret}, nil +} + // IGroupFactory defines the signature for the factory functions that may be -// registred using RegisterGroupType to provide a new implementation of the +// registered using RegisterGroupType to provide a new implementation of the // corresponding type choice. The factory function should create a new *Group // with the underlying value created based on the provided input. The range of // valid inputs is up to the specific type choice implementation, however it @@ -128,7 +166,8 @@ func MustNewUUIDGroup(val any) *Group { type IGroupFactory func(any) (*Group, error) var groupValueRegister = map[string]IGroupFactory{ - UUIDType: NewUUIDGroup, + UUIDType: NewUUIDGroup, + BytesType: NewBytesGroup, } // RegisterGroupType registers a new IGroupValue implementation diff --git a/comid/group_test.go b/comid/group_test.go index 4363f3b2..59c1f03c 100644 --- a/comid/group_test.go +++ b/comid/group_test.go @@ -1,6 +1,8 @@ package comid import ( + "encoding/binary" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -21,6 +23,11 @@ func (o testGroup) Type() string { func (o testGroup) String() string { return "test" } +func (o testGroup) Bytes() []byte { + var ret [8]byte + binary.BigEndian.PutUint64(ret[:], uint64(o)) + return ret[:] +} func (o testGroup) Valid() error { return nil @@ -60,3 +67,125 @@ func TestGroup_UmarshalJSON(t *testing.T) { err = group.UnmarshalJSON([]byte(`{"type":"uuid","value":"aaaa"}`)) assert.EqualError(t, err, "cannot unmarshal group: bad UUID: invalid UUID length: 4") } + +func Test_NewBytesGroup_OK(t *testing.T) { + var testBytes = []byte{0x01, 0x02, 0x03, 0x04} + + for _, v := range []any{ + testBytes, + &testBytes, + string(testBytes), + } { + group, err := NewBytesGroup(v) + require.NoError(t, err) + got := group.Bytes() + assert.Equal(t, testBytes[:], got) + } +} + +func Test_NewBytesGroup_NOK(t *testing.T) { + for _, tv := range []struct { + Name string + Input any + Err string + }{ + + { + Name: "invalid input integer", + Input: 7, + Err: "unexpected type for bytes: int", + }, + { + Name: "invalid input fixed array", + Input: [3]byte{0x01, 0x02, 0x03}, + Err: "unexpected type for bytes: [3]uint8", + }, + } { + t.Run(tv.Name, func(t *testing.T) { + _, err := NewBytesGroup(tv.Input) + assert.EqualError(t, err, tv.Err) + }) + } +} + +func TestGroup_MarshalCBOR_Bytes(t *testing.T) { + tv, err := NewBytesGroup(TestBytes) + require.NoError(t, err) + // 560 (h'458999786556') + // tag(560): d9 0230 + expected := MustHexDecode(t, "d90230458999786556") + + actual, err := tv.MarshalCBOR() + fmt.Printf("CBOR: %x\n", actual) + + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +func TestGroup_UnmarshalCBOR_Bytes_OK(t *testing.T) { + tv := MustHexDecode(t, "d90230458999786556") + + var actual Group + err := actual.UnmarshalCBOR(tv) + + assert.Nil(t, err) + assert.Equal(t, "bytes", actual.Type()) + assert.Equal(t, TestBytes, actual.Bytes()) +} + +func TestGroup_MarshalJSONBytes_OK(t *testing.T) { + testBytes := []byte{0x01, 0x02, 0x03} + tb := TaggedBytes(testBytes) + tv := Group{&tb} + jsonBytes, err := tv.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, `{"type":"bytes","value":"AQID"}`, string(jsonBytes)) + +} + +func TestGroup_UnmarshalJSON_Bytes_OK(t *testing.T) { + for _, tv := range []struct { + Name string + Input string + }{ + { + Name: "valid input test 1", + Input: `{ "type": "bytes", "value": "MTIzNDU2Nzg5" }`, + }, + { + Name: "valid input test 2", + Input: `{ "type": "bytes", "value": "deadbeef"}`, + }, + } { + t.Run(tv.Name, func(t *testing.T) { + var actual Group + err := actual.UnmarshalJSON([]byte(tv.Input)) + require.NoError(t, err) + }) + } +} + +func TestGroup_UnmarshalJSON_Bytes_NOK(t *testing.T) { + for _, tv := range []struct { + Name string + Input string + Err string + }{ + { + Name: "invalid value", + Input: `{ "type": "bytes", "value": "/0" }`, + Err: "cannot unmarshal class id: illegal base64 data at input byte 0", + }, + { + Name: "invalid input", + Input: `{ "type": "bytes", "value": 10 }`, + Err: "cannot unmarshal class id: json: cannot unmarshal number into Go value of type comid.TaggedBytes", + }, + } { + t.Run(tv.Name, func(t *testing.T) { + var actual ClassID + err := actual.UnmarshalJSON([]byte(tv.Input)) + assert.EqualError(t, err, tv.Err) + }) + } +} diff --git a/comid/instance.go b/comid/instance.go index 83631b32..39c40b98 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -8,7 +8,7 @@ import ( "github.com/veraison/corim/extensions" ) -// Instance stores an instance identity. The supported formats are UUID and UEID. +// Instance stores an instance identity. The supported formats are UUID, UEID and variable length opaque bytes. type Instance struct { Value IInstanceValue } @@ -77,6 +77,7 @@ func (o *Instance) UnmarshalCBOR(data []byte) error { // // ueid: base64-encoded bytes, e.g. "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" // uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" +// bytes: a variable length opaque bytes, example {0x07, 0x12, 0x34} func (o *Instance) UnmarshalJSON(data []byte) error { var tnv encoding.TypeAndValue @@ -149,7 +150,6 @@ func MustNewUEIDInstance(val any) *Instance { if err != nil { panic(err) } - return ret } @@ -167,6 +167,17 @@ func NewUUIDInstance(val any) (*Instance, error) { return &Instance{ret}, nil } +// NewBytesInstance creates a new instance of type bytes +// The supplied interface parameter could be +// a byte slice, a pointer to a byte slice or a string +func NewBytesInstance(val any) (*Instance, error) { + ret, err := NewBytes(val) + if err != nil { + return nil, err + } + return &Instance{ret}, nil +} + // MustNewUUIDInstance is like NewUUIDInstance execept it does not return an // error, assuming that the provided value is valid. It panics if that isn't // the case. @@ -180,7 +191,7 @@ func MustNewUUIDInstance(val any) *Instance { } // IInstanceFactory defines the signature for the factory functions that may be -// registred using RegisterInstanceType to provide a new implementation of the +// registered using RegisterInstanceType to provide a new implementation of the // corresponding type choice. The factory function should create a new *Instance // with the underlying value created based on the provided input. The range of // valid inputs is up to the specific type choice implementation, however it @@ -190,8 +201,9 @@ func MustNewUUIDInstance(val any) *Instance { type IInstanceFactory func(any) (*Instance, error) var instanceValueRegister = map[string]IInstanceFactory{ - UEIDType: NewUEIDInstance, - UUIDType: NewUUIDInstance, + UEIDType: NewUEIDInstance, + UUIDType: NewUUIDInstance, + BytesType: NewBytesInstance, } // RegisterInstanceType registers a new IInstanceValue implementation (created diff --git a/comid/instance_test.go b/comid/instance_test.go index 6ea22fd4..3b81d4df 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -2,6 +2,7 @@ package comid import ( "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -67,3 +68,125 @@ func Test_RegisterInstanceType(t *testing.T) { require.NoError(t, err) assert.Equal(t, instance.Bytes(), out2.Bytes()) } + +func Test_NewBytesInstance_OK(t *testing.T) { + var testBytes = []byte{0x01, 0x02, 0x03, 0x04} + + for _, v := range []any{ + testBytes, + &testBytes, + string(testBytes), + } { + instance, err := NewBytesInstance(v) + require.NoError(t, err) + got := instance.Bytes() + assert.Equal(t, testBytes[:], got) + } +} + +func Test_NewBytesInstance_NOK(t *testing.T) { + for _, tv := range []struct { + Name string + Input any + Err string + }{ + + { + Name: "invalid input integer", + Input: 7, + Err: "unexpected type for bytes: int", + }, + { + Name: "invalid input fixed array", + Input: [3]byte{0x01, 0x02, 0x03}, + Err: "unexpected type for bytes: [3]uint8", + }, + } { + t.Run(tv.Name, func(t *testing.T) { + _, err := NewBytesInstance(tv.Input) + assert.EqualError(t, err, tv.Err) + }) + } +} + +func TestInstance_MarshalCBOR_Bytes(t *testing.T) { + tv, err := NewBytesInstance(TestBytes) + require.NoError(t, err) + // 560 (h'458999786556') + // tag(560): d9 0230 + expected := MustHexDecode(t, "d90230458999786556") + + actual, err := tv.MarshalCBOR() + fmt.Printf("CBOR: %x\n", actual) + + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +func TestInstance_UnmarshalCBOR_Bytes_OK(t *testing.T) { + tv := MustHexDecode(t, "d90230458999786556") + + var actual Instance + err := actual.UnmarshalCBOR(tv) + + assert.Nil(t, err) + assert.Equal(t, "bytes", actual.Type()) + assert.Equal(t, TestBytes, actual.Bytes()) +} + +func TestInstance_MarshalJSONBytes_OK(t *testing.T) { + testBytes := []byte{0x01, 0x02, 0x03} + tb := TaggedBytes(testBytes) + tv := Instance{&tb} + jsonBytes, err := tv.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, `{"type":"bytes","value":"AQID"}`, string(jsonBytes)) + +} + +func TestInstance_UnmarshalJSON_Bytes_OK(t *testing.T) { + for _, tv := range []struct { + Name string + Input string + }{ + { + Name: "valid input test 1", + Input: `{ "type": "bytes", "value": "MTIzNDU2Nzg5" }`, + }, + { + Name: "valid input test 2", + Input: `{ "type": "bytes", "value": "deadbeef"}`, + }, + } { + t.Run(tv.Name, func(t *testing.T) { + var actual Instance + err := actual.UnmarshalJSON([]byte(tv.Input)) + require.NoError(t, err) + }) + } +} + +func TestInstance_UnmarshalJSON_Bytes_NOK(t *testing.T) { + for _, tv := range []struct { + Name string + Input string + Err string + }{ + { + Name: "invalid value", + Input: `{ "type": "bytes", "value": "/0" }`, + Err: "cannot unmarshal instance: illegal base64 data at input byte 0", + }, + { + Name: "invalid input", + Input: `{ "type": "bytes", "value": 10 }`, + Err: "cannot unmarshal instance: json: cannot unmarshal number into Go value of type comid.TaggedBytes", + }, + } { + t.Run(tv.Name, func(t *testing.T) { + var actual Instance + err := actual.UnmarshalJSON([]byte(tv.Input)) + assert.EqualError(t, err, tv.Err) + }) + } +} diff --git a/comid/rawvalue.go b/comid/rawvalue.go index 140b5f8c..5f7554c3 100644 --- a/comid/rawvalue.go +++ b/comid/rawvalue.go @@ -13,16 +13,17 @@ type RawValue struct { val interface{} } -// TaggedRawValueBytes is an alias for []byte to allow its automatic tagging -type TaggedRawValueBytes []byte - func NewRawValue() *RawValue { return &RawValue{} } func (o *RawValue) SetBytes(val []byte) *RawValue { if o != nil { - o.val = TaggedRawValueBytes(val) + v, err := NewBytes(val) + if err != nil { + return nil + } + o.val = *v } return o } @@ -33,7 +34,7 @@ func (o RawValue) GetBytes() ([]byte, error) { } switch t := o.val.(type) { - case TaggedRawValueBytes: + case TaggedBytes: return []byte(t), nil default: return nil, fmt.Errorf("unknown type %T for $raw-value-type-choice", t) @@ -45,7 +46,7 @@ func (o RawValue) MarshalCBOR() ([]byte, error) { } func (o *RawValue) UnmarshalCBOR(data []byte) error { - var rawValue TaggedRawValueBytes + var rawValue TaggedBytes if dm.Unmarshal(data, &rawValue) == nil { o.val = rawValue @@ -56,7 +57,7 @@ func (o *RawValue) UnmarshalCBOR(data []byte) error { } // UnmarshalJSON deserializes the type'n'value JSON object into the target RawValue. -// The only supported type is "bytes" with value +// The only supported type is BytesType with value func (o *RawValue) UnmarshalJSON(data []byte) error { var v tnv @@ -65,7 +66,7 @@ func (o *RawValue) UnmarshalJSON(data []byte) error { } switch v.Type { - case "bytes": + case BytesType: var x []byte if err := json.Unmarshal(v.Value, &x); err != nil { return fmt.Errorf( @@ -73,7 +74,7 @@ func (o *RawValue) UnmarshalJSON(data []byte) error { err, ) } - o.val = TaggedRawValueBytes(x) + o.val = TaggedBytes(x) default: return fmt.Errorf("unknown type %s for $raw-value-type-choice", v.Type) } @@ -89,12 +90,12 @@ func (o RawValue) MarshalJSON() ([]byte, error) { ) switch t := o.val.(type) { - case TaggedRawValueBytes: + case TaggedBytes: b, err = json.Marshal(o.val) if err != nil { return nil, err } - v = tnv{Type: "bytes", Value: b} + v = tnv{Type: BytesType, Value: b} default: return nil, fmt.Errorf("unknown type %T for raw-value-type-choice", t) } diff --git a/comid/rawvalue_test.go b/comid/rawvalue_test.go index d890794f..c5ac0e04 100644 --- a/comid/rawvalue_test.go +++ b/comid/rawvalue_test.go @@ -33,7 +33,7 @@ func TestRawValue_Get_Bytes_nok(t *testing.T) { assert.EqualError(t, err, expectedErr) } -func TestRawValue_Marshal_UnMarshal_ok(t *testing.T) { +func TestRawValue_Marshal_UnMarshal_JSON_ok(t *testing.T) { tv := RawValue{} rv := tv.SetBytes([]byte{0x01, 0x02, 0x03}) bytes, err := rv.MarshalJSON() @@ -43,3 +43,14 @@ func TestRawValue_Marshal_UnMarshal_ok(t *testing.T) { assert.NoError(t, err) assert.Equal(t, *rv, sv) } + +func TestRawValue_Marshal_UnMarshal_CBOR_ok(t *testing.T) { + tv := RawValue{} + rv := tv.SetBytes([]byte{0x01, 0x02, 0x03}) + bytes, err := rv.MarshalCBOR() + assert.NoError(t, err) + sv := RawValue{} + err = sv.UnmarshalCBOR(bytes) + assert.NoError(t, err) + assert.Equal(t, *rv, sv) +} diff --git a/comid/test_vars.go b/comid/test_vars.go index 37b9be1a..ef647dad 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -28,6 +28,7 @@ var ( TestRegID = "https://acme.example" TestMACaddr, _ = net.ParseMAC("02:00:5e:10:00:00:00:01") TestIPaddr = net.ParseIP("2001:db8::68") + TestBytes = []byte{0x89, 0x99, 0x78, 0x65, 0x56} TestUEIDString = "02deadbeefdead" TestUEID = eat.UEID(MustHexDecode(nil, TestUEIDString)) TestSignerID = MustHexDecode(nil, "acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b") From 9060081cd18008e1204bf2baee44cf7cdad19b70 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Tue, 23 Jan 2024 06:46:38 -0500 Subject: [PATCH 029/110] Introduce review comments- Part 1 Signed-off-by: Yogesh Deshpande --- comid/classid.go | 2 +- comid/group.go | 4 ++-- comid/group_test.go | 1 - comid/instance.go | 4 ++-- comid/instance_test.go | 1 - 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/comid/classid.go b/comid/classid.go index 706e1f8d..1d69f605 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -16,7 +16,7 @@ import ( ) // ClassID identifies the environment via a well-known identifier. This can be -// an OID, a UUID, a variable length opaque bytes or a profile-defined extension type. +// an OID, a UUID, variable-length opaque bytes or a profile-defined extension type. type ClassID struct { Value IClassIDValue } diff --git a/comid/group.go b/comid/group.go index 0678fb5e..c909a36f 100644 --- a/comid/group.go +++ b/comid/group.go @@ -12,7 +12,7 @@ import ( "github.com/veraison/corim/extensions" ) -// Group stores a group identity. The supported format is UUID and a variable length opaque bytes. +// Group stores a group identity. The supported formats are UUID and variable-length opaque bytes. type Group struct { Value IGroupValue } @@ -82,7 +82,7 @@ func (o *Group) UnmarshalCBOR(data []byte) error { // // { // "type": "bytes", -// "value": "69E027B2-7157-4758-BCB4-D9F167FE49EA" +// "value": "MTIzNDU2Nzg5" // } func (o *Group) UnmarshalJSON(data []byte) error { var tnv encoding.TypeAndValue diff --git a/comid/group_test.go b/comid/group_test.go index 59c1f03c..91c19596 100644 --- a/comid/group_test.go +++ b/comid/group_test.go @@ -140,7 +140,6 @@ func TestGroup_MarshalJSONBytes_OK(t *testing.T) { jsonBytes, err := tv.MarshalJSON() require.NoError(t, err) assert.Equal(t, `{"type":"bytes","value":"AQID"}`, string(jsonBytes)) - } func TestGroup_UnmarshalJSON_Bytes_OK(t *testing.T) { diff --git a/comid/instance.go b/comid/instance.go index 39c40b98..8d51bd9c 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -8,7 +8,7 @@ import ( "github.com/veraison/corim/extensions" ) -// Instance stores an instance identity. The supported formats are UUID, UEID and variable length opaque bytes. +// Instance stores an instance identity. The supported formats are UUID, UEID and variable-length opaque bytes. type Instance struct { Value IInstanceValue } @@ -77,7 +77,7 @@ func (o *Instance) UnmarshalCBOR(data []byte) error { // // ueid: base64-encoded bytes, e.g. "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" // uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" -// bytes: a variable length opaque bytes, example {0x07, 0x12, 0x34} +// bytes: a variable-length opaque byte string, example {0x07, 0x12, 0x34} func (o *Instance) UnmarshalJSON(data []byte) error { var tnv encoding.TypeAndValue diff --git a/comid/instance_test.go b/comid/instance_test.go index 3b81d4df..4907f128 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -90,7 +90,6 @@ func Test_NewBytesInstance_NOK(t *testing.T) { Input any Err string }{ - { Name: "invalid input integer", Input: 7, From 6226b49e3bdbc691b2af04e1afb79c8a1a82f959 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Tue, 23 Jan 2024 07:15:34 -0500 Subject: [PATCH 030/110] Remove some linters Signed-off-by: Yogesh Deshpande --- comid/classid_test.go | 2 +- encoding/cbor.go | 27 +++++++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/comid/classid_test.go b/comid/classid_test.go index a9100031..1763c2b5 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -407,7 +407,7 @@ func (o *testClassID) UnmarshalJSON(data []byte) error { return fmt.Errorf("bad testClassID: decoded %d bytes, want 4", len(out)) } - copy((*o)[:], []byte(out)) + copy((*o)[:], out) return nil } diff --git a/encoding/cbor.go b/encoding/cbor.go index 5d7a3533..6ef87425 100644 --- a/encoding/cbor.go +++ b/encoding/cbor.go @@ -244,17 +244,17 @@ func (o *structFieldsCBOR) ToCBOR(em cbor.EncMode) ([]byte, error) { if mapLen == 0 { return []byte{header}, nil } else if mapLen < 24 { - header = header | byte(mapLen) + header |= byte(mapLen) out = append(out, header) } else if mapLen <= math.MaxUint8 { - header = header | byte(24) + header |= byte(24) out = append(out, header, uint8(mapLen)) } else if mapLen <= math.MaxUint16 { - header = header | byte(25) + header |= byte(25) out = append(out, header) out = binary.BigEndian.AppendUint16(out, uint16(mapLen)) } else { - header = header | byte(26) + header |= byte(26) out = append(out, header) out = binary.BigEndian.AppendUint32(out, uint32(mapLen)) } @@ -360,7 +360,7 @@ func (o *structFieldsCBOR) unmarshalKeyValue(dm cbor.DecMode, rest []byte) ([]by return rest, fmt.Errorf("could not unmarshal value: %w", err) } - if err = o.Add(key, val); err != nil { + if err := o.Add(key, val); err != nil { return rest, err } @@ -370,40 +370,39 @@ func (o *structFieldsCBOR) unmarshalKeyValue(dm cbor.DecMode, rest []byte) ([]by func processAdditionalInfo( additionalInfo byte, data []byte, -) (int, []byte, error) { - var val int - rest := data +) (mapLen int, rest []byte, err error) { + rest = data if additionalInfo < 24 { - val = int(additionalInfo) + mapLen = int(additionalInfo) } else if additionalInfo < 28 { switch additionalInfo - 23 { case 1: if len(data) < 1 { return 0, nil, errors.New("unexpected EOF") } - val = int(data[0]) + mapLen = int(data[0]) rest = data[1:] case 2: if len(data) < 2 { return 0, nil, errors.New("unexpected EOF") } - val = int(binary.BigEndian.Uint16(data[:2])) + mapLen = int(binary.BigEndian.Uint16(data[:2])) rest = data[2:] case 3: if len(data) < 4 { return 0, nil, errors.New("unexpected EOF") } - val = int(binary.BigEndian.Uint32(data[:4])) + mapLen = int(binary.BigEndian.Uint32(data[:4])) rest = data[4:] default: return 0, nil, errors.New("cbor: cannot decode length value of 8 bytes") } } else if additionalInfo == 31 { - val = 0 // indefinite encoding + mapLen = 0 // indefinite encoding } else { return 0, nil, fmt.Errorf("cbor: unexpected additional information value %d", additionalInfo) } - return val, rest, nil + return mapLen, rest, nil } From dfa7ca70c98859ce3a0b6a0a0bd55f2dd9f64040 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Tue, 23 Jan 2024 07:25:17 -0500 Subject: [PATCH 031/110] Completing the remainder of the comments Signed-off-by: Yogesh Deshpande --- comid/classid_test.go | 2 +- comid/group_test.go | 2 +- comid/instance_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/comid/classid_test.go b/comid/classid_test.go index 1763c2b5..28a9ce38 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -453,7 +453,7 @@ func Test_NewBytesClassID_OK(t *testing.T) { classID, err := NewBytesClassID(v) require.NoError(t, err) got := classID.Bytes() - assert.Equal(t, testBytes[:], got) + assert.Equal(t, testBytes, got) } } diff --git a/comid/group_test.go b/comid/group_test.go index 91c19596..e415bccc 100644 --- a/comid/group_test.go +++ b/comid/group_test.go @@ -79,7 +79,7 @@ func Test_NewBytesGroup_OK(t *testing.T) { group, err := NewBytesGroup(v) require.NoError(t, err) got := group.Bytes() - assert.Equal(t, testBytes[:], got) + assert.Equal(t, testBytes, got) } } diff --git a/comid/instance_test.go b/comid/instance_test.go index 4907f128..045b749f 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -154,7 +154,7 @@ func TestInstance_UnmarshalJSON_Bytes_OK(t *testing.T) { }, { Name: "valid input test 2", - Input: `{ "type": "bytes", "value": "deadbeef"}`, + Input: `{ "type": "bytes", "value": "CgsMDQ4="}`, }, } { t.Run(tv.Name, func(t *testing.T) { From eb0a3b5100aaecd2570ba9cf759517189c5d30fd Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Fri, 26 Jan 2024 14:45:04 -0500 Subject: [PATCH 032/110] Add Integrity Registers Fixes #108 Signed-off-by: Yogesh Deshpande --- comid/cbor.go | 1 + comid/integrityregisters.go | 138 ++++++++++++++ comid/integrityregisters_test.go | 302 +++++++++++++++++++++++++++++++ comid/measurement.go | 24 +-- 4 files changed, 453 insertions(+), 12 deletions(-) create mode 100644 comid/integrityregisters.go create mode 100644 comid/integrityregisters_test.go diff --git a/comid/cbor.go b/comid/cbor.go index f4100783..b613e21b 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -57,6 +57,7 @@ func comidTags() cbor.TagSet { func initCBOREncMode() (en cbor.EncMode, err error) { encOpt := cbor.EncOptions{ + Sort: cbor.SortCoreDeterministic, IndefLength: cbor.IndefLengthForbidden, TimeTag: cbor.EncTagRequired, } diff --git a/comid/integrityregisters.go b/comid/integrityregisters.go new file mode 100644 index 00000000..9a40850e --- /dev/null +++ b/comid/integrityregisters.go @@ -0,0 +1,138 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package comid + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/veraison/swid" +) + +const TextType = "text" + +// IRegisterIndex is the interface to hold register index +// Supported index types are uint and text +type IRegisterIndex interface{} + +// IntegrityRegisters holds the Integrity Registers +type IntegrityRegisters struct { + m map[IRegisterIndex]Digests +} + +func NewIntegrityRegisters() *IntegrityRegisters { + return &IntegrityRegisters{m: make(map[IRegisterIndex]Digests)} +} + +// AddDigests allows inserting an array of digests at a specific index +// Supported index types are uint and text +func (i *IntegrityRegisters) AddDigests(index IRegisterIndex, digests Digests) error { + if len(digests) == 0 { + return fmt.Errorf("no digests to add") + } + for _, digest := range digests { + if err := i.AddDigest(index, digest); err != nil { + return fmt.Errorf("unable to add Digest: %w", err) + } + } + return nil +} + +// AddDigest allows inserting a digest at a specific index +// Supported index types are uint and text +func (i *IntegrityRegisters) AddDigest(index IRegisterIndex, digest swid.HashEntry) error { + if i.m == nil { + return fmt.Errorf("no register to add digest") + } + switch t := index.(type) { + case string, uint, uint64: + i.m[t] = append(i.m[t], digest) + default: + return fmt.Errorf("unexpected type for index: %T", t) + } + return nil +} + +func (i IntegrityRegisters) MarshalCBOR() ([]byte, error) { + return em.Marshal(i.m) +} + +func (i *IntegrityRegisters) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &i.m) +} + +type keyTypeandVal struct { + KeyType string `json:"key_type"` + Value json.RawMessage +} + +func (i IntegrityRegisters) MarshalJSON() ([]byte, error) { + jmap := make(map[string]json.RawMessage) + var newkey string + for key, val := range i.m { + var ktv keyTypeandVal + switch t := key.(type) { + case uint, uint64: + ktv.KeyType = UintType + newkey = fmt.Sprintf("%v", key) + case string: + ktv.KeyType = TextType + newkey = key.(string) + default: + return nil, fmt.Errorf("unknown type %T for index-type-choice", t) + } + + newval, err := json.Marshal(val) + if err != nil { + return nil, err + } + ktv.Value = newval + Value, err := json.Marshal(ktv) + if err != nil { + return nil, err + } + jmap[newkey] = Value + } + return json.Marshal(jmap) +} + +func (i *IntegrityRegisters) UnmarshalJSON(data []byte) error { + if i.m == nil { + i.m = make(map[IRegisterIndex]Digests) + } + jmap := make(map[string]json.RawMessage) + var index IRegisterIndex + if err := json.Unmarshal(data, &jmap); err != nil { + return fmt.Errorf("register map decoding failure: %w", err) + } + for key, val := range jmap { + var ktv keyTypeandVal + var d Digests + + if err := json.Unmarshal(val, &ktv); err != nil { + return fmt.Errorf("unable to unmarshal keyTypeAndValue: %w", err) + } + if err := json.Unmarshal(ktv.Value, &d); err != nil { + return fmt.Errorf("unable to unmarshal Digests: %w", err) + } + switch ktv.KeyType { + case UintType: + u, err := strconv.Atoi(key) + if err != nil { + return fmt.Errorf("unable to convert key to uint: %w", err) + } else if u < 0 { + return fmt.Errorf("invalid negative integer key") + } + index = uint(u) + case TextType: + index = key + default: + return fmt.Errorf("unexpected key type for index: %s", ktv.KeyType) + } + if err := i.AddDigests(index, d); err != nil { + return fmt.Errorf("unable to add digests into register set: %w", err) + } + } + return nil +} diff --git a/comid/integrityregisters_test.go b/comid/integrityregisters_test.go new file mode 100644 index 00000000..07c6cfe9 --- /dev/null +++ b/comid/integrityregisters_test.go @@ -0,0 +1,302 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/swid" +) + +func prepareRegister(t *testing.T, iType string) (*IntegrityRegisters, error) { + var err error + val := MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75") + entry := swid.HashEntry{HashAlgID: swid.Sha256, HashValue: val} + reg := NewIntegrityRegisters() + for index := 0; index < 5; index++ { + switch iType { + case "uint": + err = reg.AddDigest(uint(index), entry) + require.NoError(t, err) + case "text": + i := fmt.Sprint(index) + err = reg.AddDigest(i, entry) + require.NoError(t, err) + default: + err = fmt.Errorf("invalid iType = %s", iType) + } + if err != nil { + return nil, err + } + } + return reg, nil +} + +func TestIntegrityRegisters_AddDigest_OK(t *testing.T) { + _, err := prepareRegister(t, "uint") + require.NoError(t, err) +} + +func TestIntegrityRegisters_AddDigest_NOK(t *testing.T) { + expectedErr := `no register to add digest` + register := IntegrityRegisters{} + err := register.AddDigest(uint(0), swid.HashEntry{}) + assert.EqualError(t, err, expectedErr) + expectedErr = `unexpected type for index: bool` + var k bool + reg, err := prepareRegister(t, "uint") + require.NoError(t, err) + err = reg.AddDigest(k, swid.HashEntry{}) + assert.EqualError(t, err, expectedErr) +} + +func TestIntegrityRegisters_AddDigests_NOK(t *testing.T) { + expectedErr := `no digests to add` + register := IntegrityRegisters{} + err := register.AddDigests(uint(0), []swid.HashEntry{}) + assert.EqualError(t, err, expectedErr) +} + +func TestIntegrityRegisters_MarshalCBOR_UIntIndex_OK(t *testing.T) { + reg, err := prepareRegister(t, "uint") + // Below is the partial CBOR Pretty notation to highlight index as uint: unsigned(0) + + // A5 # map(5) + // 00 # unsigned(0) + // 81 # array(1) + // 82 # array(2) + // 01 # unsigned(1) + // 58 20 # bytes(32) + expected := MustHexDecode(t, "a5008182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75018182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75028182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75038182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75048182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75") + require.NoError(t, err) + actual, err := reg.MarshalCBOR() + require.NoError(t, err) + fmt.Printf("CBOR Payload = %x\n", actual) + assert.Equal(t, expected, actual) +} + +func TestIntegrityRegisters_UnmarshalCBOR_UIntIndex_OK(t *testing.T) { + expected := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + bstr := MustHexDecode(nil, `a5008182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75018182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75028182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75038182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75048182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75`) + actual := IntegrityRegisters{} + err := actual.UnmarshalCBOR(bstr) + require.NoError(t, err) + require.True(t, reflect.DeepEqual(expected, actual)) +} + +func TestIntegrityRegisters_MarshalJSON_UIntIndex_OK(t *testing.T) { + expected := `{ + "0": { + "key_type": "uint", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + }, + "1": { + "key_type": "uint", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + }, + "2": { + "key_type": "uint", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + }, + "3": { + "key_type": "uint", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + }, + "4": { + "key_type": "uint", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + } + } + ` + reg, err := prepareRegister(t, "uint") + require.NoError(t, err) + actual, err := reg.MarshalJSON() + require.NoError(t, err) + assert.JSONEq(t, expected, string(actual)) +} + +func TestIntegrityRegisters_MarshalCBOR_TextIndex_OK(t *testing.T) { + reg, err := prepareRegister(t, "text") + // Below is the partial CBOR Pretty notation to highlight index as text: "0" + + // A5 # map(5) + // 61 # text(1) + // 30 # "0" + // 81 # array(1) + // 82 # array(2) + // 01 # unsigned(1) + // 58 20 # bytes(32) + expected := MustHexDecode(t, `a561308182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561318182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561328182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561338182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561348182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75`) + require.NoError(t, err) + actual, err := reg.MarshalCBOR() + require.NoError(t, err) + fmt.Printf("CBOR Payload = %x\n", actual) + assert.Equal(t, expected, actual) +} + +func TestIntegrityRegisters_UnmarshalCBOR_TextIndex_OK(t *testing.T) { + expected := IntegrityRegisters{map[IRegisterIndex]Digests{ + "0": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "1": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "2": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "3": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "4": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + bstr := MustHexDecode(t, `a561308182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561318182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561328182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561338182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561348182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75`) + actual := IntegrityRegisters{} + err := actual.UnmarshalCBOR(bstr) + require.NoError(t, err) + require.True(t, reflect.DeepEqual(expected, actual)) +} + +func TestIntegrityRegisters_MarshalJSON_TextIndex_OK(t *testing.T) { + expected := `{ + "0": { + "key_type": "text", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + }, + "1": { + "key_type": "text", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + }, + "2": { + "key_type": "text", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + }, + "3": { + "key_type": "text", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + }, + "4": { + "key_type": "text", + "Value": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + ] + } + } + ` + reg, err := prepareRegister(t, "text") + require.NoError(t, err) + actual, err := reg.MarshalJSON() + require.NoError(t, err) + fmt.Printf("JSON Payload = %s", actual) + assert.JSONEq(t, expected, string(actual)) +} + +func TestIntegrityRegisters_UnmarshalJSON_TextIndex_OK(t *testing.T) { + j := `{"abcd":{"key_type":"text","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}` + expected := IntegrityRegisters{map[IRegisterIndex]Digests{ + "abcd": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + actual := IntegrityRegisters{} + err := actual.UnmarshalJSON([]byte(j)) + require.NoError(t, err) + require.True(t, reflect.DeepEqual(expected, actual)) +} + +func TestIntegrityRegisters_UnmarshalJSON_UIntIndex_OK(t *testing.T) { + j := `{ + "0":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "1":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "2":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "3":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "4":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]} + }` + expected := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + actual := IntegrityRegisters{} + err := actual.UnmarshalJSON([]byte(j)) + require.NoError(t, err) + require.True(t, reflect.DeepEqual(expected, actual)) +} + +func TestIntegrityRegisters_UnmarshalJSON_TextUInt_Index_OK(t *testing.T) { + j := `{ + "0":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "1":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "2":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "3":{"key_type":"text","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "4":{"key_type":"text","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]} + }` + expected := IntegrityRegisters{ + map[IRegisterIndex]Digests{ + uint(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "3": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "4": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + actual := IntegrityRegisters{} + err := actual.UnmarshalJSON([]byte(j)) + require.NoError(t, err) + require.True(t, reflect.DeepEqual(expected, actual)) +} + +func TestIntegrityRegisters_UnmarshalJSON_NOK(t *testing.T) { + for _, tv := range []struct { + Name string + Input string + Err string + }{ + { + Name: "invalid input integer", + Input: `{"0":{"key_type":"int","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Err: "unexpected key type for index: int", + }, + { + Name: "negative index", + Input: `{"-1":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Err: `invalid negative integer key`, + }, + { + Name: "not an integer", + Input: `{"0.2345":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Err: `unable to convert key to uint: strconv.Atoi: parsing "0.2345": invalid syntax`, + }, + { + Name: "invalid digest", + Input: `{"1":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Err: `unable to unmarshal Digests: illegal base64 data at input byte 40`, + }, + } { + t.Run(tv.Name, func(t *testing.T) { + reg := IntegrityRegisters{} + err := reg.UnmarshalJSON([]byte(tv.Input)) + assert.EqualError(t, err, tv.Err) + }) + } +} diff --git a/comid/measurement.go b/comid/measurement.go index f942855a..77d76211 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -299,18 +299,18 @@ func RegisterMkeyType(tag uint64, factory IMkeyFactory) error { // Mval stores a measurement-values-map with JSON and CBOR serializations. type Mval struct { - Ver *Version `cbor:"0,keyasint,omitempty" json:"version,omitempty"` - SVN *SVN `cbor:"1,keyasint,omitempty" json:"svn,omitempty"` - Digests *Digests `cbor:"2,keyasint,omitempty" json:"digests,omitempty"` - Flags *FlagsMap `cbor:"3,keyasint,omitempty" json:"flags,omitempty"` - RawValue *RawValue `cbor:"4,keyasint,omitempty" json:"raw-value,omitempty"` - RawValueMask *[]byte `cbor:"5,keyasint,omitempty" json:"raw-value-mask,omitempty"` - MACAddr *MACaddr `cbor:"6,keyasint,omitempty" json:"mac-addr,omitempty"` - IPAddr *net.IP `cbor:"7,keyasint,omitempty" json:"ip-addr,omitempty"` - SerialNumber *string `cbor:"8,keyasint,omitempty" json:"serial-number,omitempty"` - UEID *eat.UEID `cbor:"9,keyasint,omitempty" json:"ueid,omitempty"` - UUID *UUID `cbor:"10,keyasint,omitempty" json:"uuid,omitempty"` - + Ver *Version `cbor:"0,keyasint,omitempty" json:"version,omitempty"` + SVN *SVN `cbor:"1,keyasint,omitempty" json:"svn,omitempty"` + Digests *Digests `cbor:"2,keyasint,omitempty" json:"digests,omitempty"` + Flags *FlagsMap `cbor:"3,keyasint,omitempty" json:"flags,omitempty"` + RawValue *RawValue `cbor:"4,keyasint,omitempty" json:"raw-value,omitempty"` + RawValueMask *[]byte `cbor:"5,keyasint,omitempty" json:"raw-value-mask,omitempty"` + MACAddr *MACaddr `cbor:"6,keyasint,omitempty" json:"mac-addr,omitempty"` + IPAddr *net.IP `cbor:"7,keyasint,omitempty" json:"ip-addr,omitempty"` + SerialNumber *string `cbor:"8,keyasint,omitempty" json:"serial-number,omitempty"` + UEID *eat.UEID `cbor:"9,keyasint,omitempty" json:"ueid,omitempty"` + UUID *UUID `cbor:"10,keyasint,omitempty" json:"uuid,omitempty"` + IntegrityRegisters *IntegrityRegisters `cbor:"14,keyasint,omitempty" json:"integrity-registers,omitempty"` Extensions } From c8d380f541247692db9f37add2269dd957fe28a5 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 7 Mar 2024 13:50:10 +0000 Subject: [PATCH 033/110] Add CCA Realm Reference Values (#109) CCA Realm Reference Values Signed-off-by: Yogesh Deshpande --- .../templates/comid-cca-realm-refval.json | 75 +++++++ .../data/corim/templates/corim-cca-realm.json | 19 ++ cocli/data/corim/templates/corim-cca.json | 6 - comid/example_cca_realm_refval_test.go | 207 ++++++++++++++++++ comid/integrityregisters.go | 4 +- comid/integrityregisters_test.go | 70 +++--- comid/measurement.go | 3 +- comid/test_vars.go | 75 +++++++ 8 files changed, 415 insertions(+), 44 deletions(-) create mode 100644 cocli/data/comid/templates/comid-cca-realm-refval.json create mode 100644 cocli/data/corim/templates/corim-cca-realm.json create mode 100644 comid/example_cca_realm_refval_test.go diff --git a/cocli/data/comid/templates/comid-cca-realm-refval.json b/cocli/data/comid/templates/comid-cca-realm-refval.json new file mode 100644 index 00000000..965d9fd2 --- /dev/null +++ b/cocli/data/comid/templates/comid-cca-realm-refval.json @@ -0,0 +1,75 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 0 + }, + "entities": [ + { + "name": "Workload Client Ltd.", + "regid": "https://workloadclient.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "uuid", + "value": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C" + }, + "vendor": "Workload Client Ltd" + }, + "instance": { + "type": "bytes", + "value": "QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" + } + }, + "measurements": [ + { + "value": { + "integrity-registers": { + "rim": { + "key-type": "text", + "value": [ + "sha-384;QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" + ] + }, + "rem0": { + "key-type": "text", + "value": [ + "sha-384;IQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" + ] + }, + "rem1": { + "key-type": "text", + "value": [ + "sha-384;JQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" + ] + }, + "rem2": { + "key-type": "text", + "value": [ + "sha-384;MQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" + ] + }, + "rem3": { + "key-type": "text", + "value": [ + "sha-384;NQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" + ] + } + } + } + } + ] + } + ] + } +} \ No newline at end of file diff --git a/cocli/data/corim/templates/corim-cca-realm.json b/cocli/data/corim/templates/corim-cca-realm.json new file mode 100644 index 00000000..8dfd86d0 --- /dev/null +++ b/cocli/data/corim/templates/corim-cca-realm.json @@ -0,0 +1,19 @@ +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "profiles": [ + "http://arm.com/cca/realm/1" + ], + "validity": { + "not-before": "2021-12-31T00:00:00Z", + "not-after": "2025-12-31T00:00:00Z" + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "acme.example", + "roles": [ + "manifestCreator" + ] + } + ] +} \ No newline at end of file diff --git a/cocli/data/corim/templates/corim-cca.json b/cocli/data/corim/templates/corim-cca.json index ba2ce068..fb28d9ba 100644 --- a/cocli/data/corim/templates/corim-cca.json +++ b/cocli/data/corim/templates/corim-cca.json @@ -1,11 +1,5 @@ { "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", - "dependent-rims": [ - { - "href": "https://parent.example/rims/ccb3aa85-61b4-40f1-848e-02ad6e8a254b", - "thumbprint": "sha-256:5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" - } - ], "profiles": [ "http://arm.com/cca/ssd/1" ], diff --git a/comid/example_cca_realm_refval_test.go b/comid/example_cca_realm_refval_test.go new file mode 100644 index 00000000..1ef644ef --- /dev/null +++ b/comid/example_cca_realm_refval_test.go @@ -0,0 +1,207 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "fmt" + "strings" +) + +func Example_cca_realm_refval() { + comid := Comid{} + + if err := comid.FromJSON([]byte(CCARealmRefValJSONTemplate)); err != nil { + panic(err) + } + + if err := comid.Valid(); err != nil { + panic(err) + } + + if err := extractRealmRefVals(&comid); err != nil { + panic(err) + } + // output: + // Vendor: Workload Client Ltd + // ClassID: cd1f0e5526f9460db9d8f7fde171787c + // InstanceID: 4284b5694ca6c0d2cf4789a0b95ac8025c818de52304364be7cd2981b2d2edc685b322277ec25819962413d8c9b2c1f5 + // Index: rim + // Alg: sha-384 + // Digest: 4284b5694ca6c0d2cf4789a0b95ac8025c818de52304364be7cd2981b2d2edc685b322277ec25819962413d8c9b2c1f5 + // Index: rem0 + // Alg: sha-384 + // Digest: 2107bbe761fca52d95136a1354db7a4dd57b1b26be0d3da71d9eb23986b34ba615abf6514cf35e5a9ea55a032d068a78 + // Index: rem1 + // Alg: sha-384 + // Digest: 2507bbe761fca52d95136a1354db7a4dd57b1b26be0d3da71d9eb23986b34ba615abf6514cf35e5a9ea55a032d068a78 + // Index: rem2 + // Alg: sha-384 + // Digest: 3107bbe761fca52d95136a1354db7a4dd57b1b26be0d3da71d9eb23986b34ba615abf6514cf35e5a9ea55a032d068a78 + // Index: rem3 + // Alg: sha-384 + // Digest: 3507bbe761fca52d95136a1354db7a4dd57b1b26be0d3da71d9eb23986b34ba615abf6514cf35e5a9ea55a032d068a78 + +} + +func extractRealmRefVals(c *Comid) error { + if c.Triples.ReferenceValues == nil { + return fmt.Errorf("no reference values triples") + } + + for i, rv := range *c.Triples.ReferenceValues { + if err := extractRealmRefVal(rv); err != nil { + return fmt.Errorf("bad Realm reference value at index %d: %w", i, err) + } + } + + return nil +} + +func extractRealmRefVal(rv ReferenceValue) error { + class := rv.Environment.Class + instance := rv.Environment.Instance + + if err := extractRealmClass(class); err != nil { + return fmt.Errorf("extracting class: %w", err) + } + + if err := extractRealmInstanceID(instance); err != nil { + return fmt.Errorf("extracting realm instanceID: %w", err) + } + + measurements := rv.Measurements + + if err := extractMeasurements(measurements); err != nil { + return fmt.Errorf("extracting measurements: %w", err) + } + + return nil +} + +func extractMeasurements(m Measurements) error { + if len(m) == 0 { + return fmt.Errorf("no measurements") + } + + for i, m := range m { + if err := extractMeasurement(m); err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + } + + return nil +} + +func extractMeasurement(m Measurement) error { + if err := extractIntegrityRegisters(m.Val.IntegrityRegisters); err != nil { + return fmt.Errorf("extracting digest: %w", err) + } + + return nil +} + +func extractRealmClass(c *Class) error { + if c == nil { + fmt.Println("class not present") + return nil + } + + if c.Vendor != nil { + fmt.Printf("Vendor: %s\n", c.GetVendor()) + } + + classID := c.ClassID + if classID == nil { + fmt.Println("class-id not present") + return nil + } + + if classID.Type() != "uuid" { + return fmt.Errorf("class id is not a uuid") + } + if err := classID.Valid(); err != nil { + return fmt.Errorf("invalid uuid: %v", err) + } + fmt.Printf("ClassID: %x\n", classID.Bytes()) + + return nil +} + +func extractRealmInstanceID(i *Instance) error { + if i == nil { + return fmt.Errorf("no instance") + } + + if i.Type() != "bytes" { + return fmt.Errorf("instance id is not bytes") + } + + fmt.Printf("InstanceID: %x\n", i.Bytes()) + + return nil +} + +func extractIntegrityRegisters(r *IntegrityRegisters) error { + if r == nil { + return fmt.Errorf("no integrity registers") + } + + keys, err := extractRegisterIndexes(r) + if err != nil { + return fmt.Errorf("unable to extract register index: %v", err) + } + + for _, k := range keys { + d, ok := r.m[k] + if !ok { + return fmt.Errorf("unable to locate register index for: %s", k) + } + fmt.Printf("Index: %s\n", k) + if err := extractRealmDigests(d); err != nil { + return fmt.Errorf("invalid Digests for key: %s, %v", k, err) + } + } + + return nil +} + +func extractRealmDigests(digests Digests) error { + + if err := digests.Valid(); err != nil { + return fmt.Errorf("invalid digest: %v", err) + } + for _, d := range digests { + fmt.Printf("Alg: %s\n", d.AlgIDToString()) + fmt.Printf("Digest: %x\n", d.HashValue) + } + + return nil +} + +func extractRegisterIndexes(r *IntegrityRegisters) ([]string, error) { + var keys [5]string + for k := range r.m { + switch t := k.(type) { + case string: + key := strings.ToLower(t) + switch key { + case "rim": + keys[0] = key + case "rem0": + keys[1] = key + case "rem1": + keys[2] = key + case "rem2": + keys[3] = key + case "rem3": + keys[4] = key + default: + return nil, fmt.Errorf("unexpected register index: %s", key) + } + default: + return nil, fmt.Errorf("unexpected type for index: %T", t) + } + } + return keys[:], nil +} diff --git a/comid/integrityregisters.go b/comid/integrityregisters.go index 9a40850e..e0aec76f 100644 --- a/comid/integrityregisters.go +++ b/comid/integrityregisters.go @@ -63,8 +63,8 @@ func (i *IntegrityRegisters) UnmarshalCBOR(data []byte) error { } type keyTypeandVal struct { - KeyType string `json:"key_type"` - Value json.RawMessage + KeyType string `json:"key-type"` + Value json.RawMessage `json:"value"` } func (i IntegrityRegisters) MarshalJSON() ([]byte, error) { diff --git a/comid/integrityregisters_test.go b/comid/integrityregisters_test.go index 07c6cfe9..079956e9 100644 --- a/comid/integrityregisters_test.go +++ b/comid/integrityregisters_test.go @@ -98,32 +98,32 @@ func TestIntegrityRegisters_UnmarshalCBOR_UIntIndex_OK(t *testing.T) { func TestIntegrityRegisters_MarshalJSON_UIntIndex_OK(t *testing.T) { expected := `{ "0": { - "key_type": "uint", - "Value": [ + "key-type": "uint", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] }, "1": { - "key_type": "uint", - "Value": [ + "key-type": "uint", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] }, "2": { - "key_type": "uint", - "Value": [ + "key-type": "uint", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] }, "3": { - "key_type": "uint", - "Value": [ + "key-type": "uint", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] }, "4": { - "key_type": "uint", - "Value": [ + "key-type": "uint", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] } @@ -173,32 +173,32 @@ func TestIntegrityRegisters_UnmarshalCBOR_TextIndex_OK(t *testing.T) { func TestIntegrityRegisters_MarshalJSON_TextIndex_OK(t *testing.T) { expected := `{ "0": { - "key_type": "text", - "Value": [ + "key-type": "text", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] }, "1": { - "key_type": "text", - "Value": [ + "key-type": "text", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] }, "2": { - "key_type": "text", - "Value": [ + "key-type": "text", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] }, "3": { - "key_type": "text", - "Value": [ + "key-type": "text", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] }, "4": { - "key_type": "text", - "Value": [ + "key-type": "text", + "value": [ "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] } @@ -213,7 +213,7 @@ func TestIntegrityRegisters_MarshalJSON_TextIndex_OK(t *testing.T) { } func TestIntegrityRegisters_UnmarshalJSON_TextIndex_OK(t *testing.T) { - j := `{"abcd":{"key_type":"text","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}` + j := `{"abcd":{"key-type":"text","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}` expected := IntegrityRegisters{map[IRegisterIndex]Digests{ "abcd": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} @@ -225,11 +225,11 @@ func TestIntegrityRegisters_UnmarshalJSON_TextIndex_OK(t *testing.T) { func TestIntegrityRegisters_UnmarshalJSON_UIntIndex_OK(t *testing.T) { j := `{ - "0":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "1":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "2":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "3":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "4":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]} + "0":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "1":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "2":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "3":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "4":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]} }` expected := IntegrityRegisters{map[IRegisterIndex]Digests{ uint(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, @@ -246,11 +246,11 @@ func TestIntegrityRegisters_UnmarshalJSON_UIntIndex_OK(t *testing.T) { func TestIntegrityRegisters_UnmarshalJSON_TextUInt_Index_OK(t *testing.T) { j := `{ - "0":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "1":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "2":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "3":{"key_type":"text","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "4":{"key_type":"text","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]} + "0":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "1":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "2":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "3":{"key-type":"text","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, + "4":{"key-type":"text","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]} }` expected := IntegrityRegisters{ map[IRegisterIndex]Digests{ @@ -274,22 +274,22 @@ func TestIntegrityRegisters_UnmarshalJSON_NOK(t *testing.T) { }{ { Name: "invalid input integer", - Input: `{"0":{"key_type":"int","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Input: `{"0":{"key-type":"int","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, Err: "unexpected key type for index: int", }, { Name: "negative index", - Input: `{"-1":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Input: `{"-1":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, Err: `invalid negative integer key`, }, { Name: "not an integer", - Input: `{"0.2345":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Input: `{"0.2345":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, Err: `unable to convert key to uint: strconv.Atoi: parsing "0.2345": invalid syntax`, }, { Name: "invalid digest", - Input: `{"1":{"key_type":"uint","Value":["sha-256;5Fty9cDAtXLbTY06t+l/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Input: `{"1":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/3TmI0eoJN7LZ6hOUiTXU="]}}`, Err: `unable to unmarshal Digests: illegal base64 data at input byte 40`, }, } { diff --git a/comid/measurement.go b/comid/measurement.go index 77d76211..469e611d 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -355,7 +355,8 @@ func (o Mval) Valid() error { o.IPAddr == nil && o.SerialNumber == nil && o.UEID == nil && - o.UUID == nil { + o.UUID == nil && + o.IntegrityRegisters == nil { return fmt.Errorf("no measurement value set") } diff --git a/comid/test_vars.go b/comid/test_vars.go index ef647dad..7c5969e7 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -439,4 +439,79 @@ var ( } } ` + CCARealmRefValJSONTemplate = `{ + "lang": "en-GB", + "tag-identity": { + "id": "99019224-57AA-44BC-BEF8-D36BDD6BD035", + "version": 0 +}, +"entities": [ + { + "name": "Workload Client Ltd.", + "regid": "https://workloadclient.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } +], +"triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "uuid", + "value": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C" + }, + "vendor": "Workload Client Ltd" + }, + "instance": { + "type": "bytes", + "value": "QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" + } + }, + "measurements": [ + { + "value": { + "integrity-registers": { + "rim": { + "key-type": "text", + "value": [ + "sha-384;QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" + ] + }, + "rem0": { + "key-type": "text", + "value": [ + "sha-384;IQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" + ] + }, + "rem1": { + "key-type": "text", + "value": [ + "sha-384;JQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" + ] + }, + "rem2": { + "key-type": "text", + "value": [ + "sha-384;MQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" + ] + }, + "rem3": { + "key-type": "text", + "value": [ + "sha-384;NQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" + ] + } + } + } + } + ] + } + ] +} +}` ) From 166cb89c8efcd59be3f8b9dee1e06c3f36720b97 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Mon, 15 Apr 2024 12:43:00 -0400 Subject: [PATCH 034/110] Provide missing API Implementations Signed-off-by: Yogesh Deshpande --- comid/classid.go | 43 +++++++++++++++++++++++++++++++++ comid/instance.go | 41 ++++++++++++++++++++++++++++++++ comid/instance_test.go | 32 ++++++++++++++++++++++--- comid/measurement.go | 44 ++++++++++++++++++++++++++++++++++ comid/measurement_test.go | 50 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 3 deletions(-) diff --git a/comid/classid.go b/comid/classid.go index 1d69f605..4cf6f631 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -138,12 +138,55 @@ func (o ClassID) String() string { return o.Value.String() } +// SetImplID sets the value of the target ClassID to the supplied PSA +// Implementation ID (see Section 3.2.2 of draft-tschofenig-rats-psa-token) +func (o *ClassID) SetImplID(implID ImplID) *ClassID { + if o != nil { + o.Value = TaggedImplID(implID) + } + return o +} + +// GetImplID retrieves the value of the PSA Implementation ID +// (see Section 3.2.2 of draft-tschofenig-rats-psa-token) from ClassID +func (o ClassID) GetImplID() (ImplID, error) { + switch t := o.Value.(type) { + case *TaggedImplID: + return ImplID(*t), nil + case TaggedImplID: + return ImplID(t), nil + default: + return ImplID{}, fmt.Errorf("class-id type is: %T", t) + } +} + type IClassIDValue interface { extensions.ITypeChoiceValue Bytes() []byte } +// SetUUID sets the value of the target ClassID to the supplied UUID +func (o *ClassID) SetUUID(uuid UUID) *ClassID { + if o != nil { + o.Value = TaggedUUID(uuid) + } + return o +} + +// SetOID sets the value of the targed ClassID to the supplied OID. +// The OID is a string in dotted-decimal notation +func (o *ClassID) SetOID(s string) *ClassID { + if o != nil { + var berOID OID + if berOID.FromString(s) != nil { + return nil + } + o.Value = TaggedOID(berOID) + } + return o +} + const ImplIDType = "psa.impl-id" type ImplID [32]byte diff --git a/comid/instance.go b/comid/instance.go index 8d51bd9c..1c1383fd 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -4,8 +4,10 @@ import ( "encoding/json" "fmt" + "github.com/google/uuid" "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" + "github.com/veraison/eat" ) // Instance stores an instance identity. The supported formats are UUID, UEID and variable-length opaque bytes. @@ -121,6 +123,45 @@ func (o Instance) MarshalJSON() ([]byte, error) { return json.Marshal(value) } +// SetUEID sets the identity of the target instance to the supplied UEID +func (o *Instance) SetUEID(val eat.UEID) *Instance { + if o != nil { + if val.Validate() != nil { + return nil + } + o.Value = TaggedUEID(val) + } + return o +} + +// SetUUID sets the identity of the target instance to the supplied UUID +func (o *Instance) SetUUID(val uuid.UUID) *Instance { + if o != nil { + o.Value = TaggedUUID(val) + } + return o +} + +func (o Instance) GetUEID() (eat.UEID, error) { + switch t := o.Value.(type) { + case TaggedUEID: + return eat.UEID(t), nil + default: + return eat.UEID{}, fmt.Errorf("instance-id type is: %T", t) + } +} + +func (o Instance) GetUUID() (UUID, error) { + switch t := o.Value.(type) { + case *TaggedUUID: + return UUID(*t), nil + case TaggedUUID: + return UUID(t), nil + default: + return UUID{}, fmt.Errorf("instance-id type is: %T", t) + } +} + // IInstanceValue is the interface implemented by all Instance value // implementations. type IInstanceValue interface { diff --git a/comid/instance_test.go b/comid/instance_test.go index 045b749f..37cafcbd 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -5,15 +5,41 @@ import ( "fmt" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestInstance_SetUUID_OK(t *testing.T) { + inst := &Instance{} + testUUID, err := uuid.Parse(TestUUIDString) + require.NoError(t, err) + i := inst.SetUUID(testUUID) + require.NotNil(t, i) +} + func TestInstance_GetUUID_OK(t *testing.T) { inst := MustNewUUIDInstance(TestUUID) - u, ok := inst.Value.(*TaggedUUID) - assert.True(t, ok) - assert.EqualValues(t, TestUUID, *u) + require.NotNil(t, inst) + u, err := inst.GetUUID() + assert.Nil(t, err) + assert.Equal(t, u, TestUUID) +} + +func TestInstance_GetUUID_NOK(t *testing.T) { + inst := &Instance{} + expectedErr := "instance-id type is: " + _, err := inst.GetUUID() + assert.EqualError(t, err, expectedErr) +} + +func TestInstance_SetGetUEID_OK(t *testing.T) { + inst := &Instance{} + inst = inst.SetUEID(TestUEID) + require.NotNil(t, inst) + expectedUEID, err := inst.GetUEID() + require.NoError(t, err) + assert.Equal(t, TestUEID, expectedUEID) } type testInstance string diff --git a/comid/measurement.go b/comid/measurement.go index 469e611d..52fac579 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -51,6 +51,11 @@ func (o Mkey) IsSet() bool { return o.Value != nil } +// Type returns the type of Mkey +func (o Mkey) Type() string { + return o.Value.Type() +} + // Valid returns nil if the Mkey is valid or an error describing the problem, // if it is not. func (o Mkey) Valid() error { @@ -65,6 +70,45 @@ func (o Mkey) Valid() error { return nil } +func (o Mkey) GetPSARefValID() (PSARefValID, error) { + if !o.IsSet() { + return PSARefValID{}, errors.New("MKey is not set") + } + switch t := o.Value.(type) { + case *TaggedPSARefValID: + return PSARefValID(*t), nil + case TaggedPSARefValID: + return PSARefValID(t), nil + default: + return PSARefValID{}, fmt.Errorf("measurement-key type is: %T", t) + } +} + +func (o Mkey) GetCCAPlatformConfigID() (CCAPlatformConfigID, error) { + if !o.IsSet() { + return "", errors.New("MKey is not set") + } + switch t := o.Value.(type) { + case *TaggedCCAPlatformConfigID: + return CCAPlatformConfigID(*t), nil + case TaggedCCAPlatformConfigID: + return CCAPlatformConfigID(t), nil + default: + return "", fmt.Errorf("measurement-key type is: %T", t) + } +} + +func (o Mkey) GetKeyUint() (uint64, error) { + switch t := o.Value.(type) { + case UintMkey: + return uint64(t), nil + case *UintMkey: + return uint64(*t), nil + default: + return MaxUint64, fmt.Errorf("measurement-key type is: %T", t) + } +} + // UnmarshalJSON deserializes the supplied JSON object into the target MKey // The key object must have the following shape: // diff --git a/comid/measurement_test.go b/comid/measurement_test.go index 04321bde..fe87223a 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -58,6 +58,33 @@ func TestMeasurement_NewPSAMeasurement_no_values(t *testing.T) { assert.EqualError(t, err, "no measurement value set") } +func TestGetPSARefValID(t *testing.T) { + psaRefValID, err := NewPSARefValID(TestSignerID) + require.NoError(t, err) + psaRefValID.SetLabel("PRoT") + psaRefValID.SetVersion("1.2.3") + mkey, err := NewMkeyPSARefvalID(psaRefValID) + require.NoError(t, err) + actual, err := mkey.GetPSARefValID() + require.NoError(t, err) + assert.Equal(t, *psaRefValID, actual) +} + +func TestGetPSARefValID_NOK(t *testing.T) { + mkey := &Mkey{} + expected := "MKey is not set" + _, err := mkey.GetPSARefValID() + assert.EqualError(t, err, expected) +} + +func TestGetPSARefValID_InvalidType(t *testing.T) { + expected := "measurement-key type is: *comid.TaggedCCAPlatformConfigID" + mkey, err := NewMkeyCCAPlatformConfigID(TestCCALabel) + require.NoError(t, err) + _, err = mkey.GetPSARefValID() + assert.EqualError(t, err, expected) +} + func TestMeasurement_NewCCAPlatCfgMeasurement_no_values(t *testing.T) { ccaplatID := CCAPlatformConfigID(TestCCALabel) @@ -68,6 +95,29 @@ func TestMeasurement_NewCCAPlatCfgMeasurement_no_values(t *testing.T) { assert.EqualError(t, err, "no measurement value set") } +func TestGetCCAPlatformConfigID(t *testing.T) { + ccaplatID := CCAPlatformConfigID(TestCCALabel) + mkey, err := NewMkeyCCAPlatformConfigID(TestCCALabel) + require.NoError(t, err) + actual, err := mkey.GetCCAPlatformConfigID() + require.NoError(t, err) + assert.Equal(t, ccaplatID, actual) +} + +func TestGetCCAPlatformConfigID_NOK(t *testing.T) { + mkey := &Mkey{} + expected := "MKey is not set" + _, err := mkey.GetCCAPlatformConfigID() + assert.EqualError(t, err, expected) +} + +func TestGetCCAPlatformConfigID_InvalidType(t *testing.T) { + mkey := &Mkey{UintMkey(10)} + expected := "measurement-key type is: comid.UintMkey" + _, err := mkey.GetCCAPlatformConfigID() + assert.EqualError(t, err, expected) +} + func TestMeasurement_NewCCAPlatCfgMeasurement_valid_meas(t *testing.T) { ccaplatID := CCAPlatformConfigID(TestCCALabel) From 2335846b8db4bb1de695ab88969a2748d65025b1 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 18 Apr 2024 07:07:00 -0400 Subject: [PATCH 035/110] Minor correction in implementation Signed-off-by: Yogesh Deshpande --- comid/instance.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/comid/instance.go b/comid/instance.go index 1c1383fd..b22f5ae1 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -146,6 +146,8 @@ func (o Instance) GetUEID() (eat.UEID, error) { switch t := o.Value.(type) { case TaggedUEID: return eat.UEID(t), nil + case *TaggedUEID: + return eat.UEID(*t), nil default: return eat.UEID{}, fmt.Errorf("instance-id type is: %T", t) } From 507d79c86bf5859390f83597b876290a15721c4e Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 30 May 2024 09:22:18 +0100 Subject: [PATCH 036/110] feat: add TLS support - Automatically use TLS API client if URL scheme specified to --api-server is HTTPS. - Add -i/--insecure flag to suppress cert validation for TLS. - Add -E/--ca-cert flag to allow specifying additional CA cert(s) to be used in TLS cert validation (by default, the system CA certs are used). Signed-off-by: Sergei Trofimov --- cocli/README.md | 9 +++++++ cocli/cmd/corimSubmit.go | 45 ++++++++++++++++++++--------------- cocli/cmd/corimSubmit_test.go | 7 ++++++ cocli/cmd/isubmitter.go | 5 ++++ go.mod | 4 ++-- go.sum | 2 ++ 6 files changed, 51 insertions(+), 21 deletions(-) diff --git a/cocli/README.md b/cocli/README.md index 0ec8c012..7d0e3969 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -560,6 +560,15 @@ path (usually `~/.config/cocli/config.yaml` on XDG-compliant systems). Please see `./data/config/example-config.yaml` file for details of the configuration that needs to be provided. +#### Note on TLS + +If the scheme in the API server URL is HTTPS, `cocli` will attempt to establish +a TLS connection to the server, validating the server certificate using system CA +certs. It is possible to disable server certificate validation with +`-i`/`--insecure` flag. Alternatively, if the CA cert for the server is +available but is not installed in the system, it may be specified using +`-E`/`--ca-cert` flag. + ## Visual Synopsis of the Available Commands ```mermaid diff --git a/cocli/cmd/corimSubmit.go b/cocli/cmd/corimSubmit.go index f231d18e..80c1bd3a 100644 --- a/cocli/cmd/corimSubmit.go +++ b/cocli/cmd/corimSubmit.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd @@ -7,17 +7,21 @@ import ( "errors" "fmt" "net/url" + "strings" "github.com/spf13/afero" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/spf13/viper" "github.com/veraison/apiclient/provisioning" ) var ( - corimFile *string - mediaType *string - apiServer string + corimFile *string + mediaType *string + apiServer string + isInsecure bool + certPaths []string ) var ( @@ -72,21 +76,18 @@ func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command { cmd.Flags().StringP("token-url", "T", "", "token URL of the OAuth2 service") cmd.Flags().StringP("username", "U", "", "service username") cmd.Flags().StringP("password", "P", "", "service password") - - err := viper.BindPFlag("api_server", cmd.Flags().Lookup("api-server")) - cobra.CheckErr(err) - err = viper.BindPFlag("auth", cmd.Flags().Lookup("auth")) - cobra.CheckErr(err) - err = viper.BindPFlag("client_id", cmd.Flags().Lookup("client-id")) - cobra.CheckErr(err) - err = viper.BindPFlag("client_secret", cmd.Flags().Lookup("client-secret")) - cobra.CheckErr(err) - err = viper.BindPFlag("username", cmd.Flags().Lookup("username")) - cobra.CheckErr(err) - err = viper.BindPFlag("password", cmd.Flags().Lookup("password")) - cobra.CheckErr(err) - err = viper.BindPFlag("token_url", cmd.Flags().Lookup("token-url")) - cobra.CheckErr(err) + cmd.Flags().BoolP( + "insecure", "i", false, "Allow insecure connections (e.g. do not verify TLS certs)", + ) + cmd.Flags().StringArrayP( + "ca-cert", "E", nil, "path to a CA cert that will be used in addition to system certs; may be specified multiple times", + ) + + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + cfgName := strings.ReplaceAll(flag.Name, "-", "_") + err := viper.BindPFlag(cfgName, flag) + cobra.CheckErr(err) + }) return cmd } @@ -109,6 +110,9 @@ func checkSubmitArgs() error { return errors.New("no media type supplied") } + isInsecure = viper.GetBool("insecure") + certPaths = viper.GetStringSlice("ca_cert") + return nil } @@ -119,6 +123,9 @@ func provisionData(data []byte, submitter ISubmitter, uri string, mediaType stri return fmt.Errorf("unable to set submit URI: %w", err) } + submitter.SetIsInsecure(isInsecure) + submitter.SetCerts(certPaths) + submitter.SetDeleteSession(true) if err := submitter.Run(data, mediaType); err != nil { return fmt.Errorf("run failed: %w", err) diff --git a/cocli/cmd/corimSubmit_test.go b/cocli/cmd/corimSubmit_test.go index 5fa29d20..b5ee04a1 100644 --- a/cocli/cmd/corimSubmit_test.go +++ b/cocli/cmd/corimSubmit_test.go @@ -1,3 +1,6 @@ +// Copyright 2021-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + package cmd import ( @@ -133,6 +136,8 @@ func Test_CorimSubmitCmd_submit_ok(t *testing.T) { require.NoError(t, err) ms.EXPECT().SetAuth(gomock.Any()) ms.EXPECT().SetSubmitURI("http://veraison.example/endorsement-provisioning/v1/submit").Return(nil) + ms.EXPECT().SetIsInsecure(false) + ms.EXPECT().SetCerts([]string{}) ms.EXPECT().SetDeleteSession(true) ms.EXPECT().Run(testSignedCorimValid, "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1").Return(nil) err = cmd.Execute() @@ -158,6 +163,8 @@ func Test_CorimSubmitCmd_submit_not_ok(t *testing.T) { require.NoError(t, err) ms.EXPECT().SetAuth(gomock.Any()) ms.EXPECT().SetSubmitURI("http://veraison.example/endorsement-provisioning/v1/submit").Return(nil) + ms.EXPECT().SetIsInsecure(false) + ms.EXPECT().SetCerts([]string{}) ms.EXPECT().SetDeleteSession(true) err = errors.New(`unexpected HTTP response code 404`) diff --git a/cocli/cmd/isubmitter.go b/cocli/cmd/isubmitter.go index 823e0ac8..cc9734ed 100644 --- a/cocli/cmd/isubmitter.go +++ b/cocli/cmd/isubmitter.go @@ -1,3 +1,6 @@ +// Copyright 2021-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + package cmd import ( @@ -11,4 +14,6 @@ type ISubmitter interface { SetAuth(a auth.IAuthenticator) SetSubmitURI(uri string) error SetDeleteSession(session bool) + SetIsInsecure(v bool) + SetCerts(paths []string) } diff --git a/go.mod b/go.mod index 96a5aa49..8b008732 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,10 @@ require ( github.com/spf13/afero v1.9.2 github.com/spf13/cast v1.4.1 github.com/spf13/cobra v1.2.1 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.8.2 - github.com/veraison/apiclient v0.2.0 + github.com/veraison/apiclient v0.2.1-0.20240531100343-8a3a730a1e94 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7 github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca @@ -37,7 +38,6 @@ require ( github.com/pelletier/go-toml v1.9.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.12.0 // indirect diff --git a/go.sum b/go.sum index e1ae4e55..dec21edf 100644 --- a/go.sum +++ b/go.sum @@ -323,6 +323,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/veraison/apiclient v0.2.0 h1:QELvZ+eEfzh9v0ORe9B2UTMpiA7aONHpZIfwSfcRR6s= github.com/veraison/apiclient v0.2.0/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A= +github.com/veraison/apiclient v0.2.1-0.20240531100343-8a3a730a1e94 h1:0d7vTs3K9Y4bskTtI3pvkFE0HiSHc4vWA3M6Fc0lWRM= +github.com/veraison/apiclient v0.2.1-0.20240531100343-8a3a730a1e94/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff h1:r6I2eJL/z8dp5flsQIKHMeDjyV6UO8If3MaVBLvTjF4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7 h1:KcKzBthSrSZIUEWBjVvkuk/DE3PyYFbXZxhx5byGFtc= From 3daa7ad9a31525e5e6cc2e62e1bedffe8432af50 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 31 May 2024 13:45:49 +0100 Subject: [PATCH 037/110] fix(github): add go setup step to CI flows This is necessary as macos-latest image does not appear to come with go installed by default. Signed-off-by: Sergei Trofimov --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 322b5d77..be89239a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,9 @@ jobs: matrix: os: [macos-latest, ubuntu-latest] steps: + - uses: actions/setup-go@v3 + with: + go-version: "1.19" - name: Checkout code uses: actions/checkout@v2 with: From 39acfdd6712fece02787742025d807add59994f4 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Tue, 23 Apr 2024 07:06:21 -0400 Subject: [PATCH 038/110] Add Realm Personalization Value as RawValue to Realm Reference Values Fix the failing CI Tests Signed-off-by: Yogesh Deshpande --- .github/workflows/ci.yml | 7 +- comid/classid.go | 37 ++++++++-- comid/classid_test.go | 95 ++++++++++++++++++++++++++ comid/example_cca_realm_refval_test.go | 27 ++++++-- comid/integrityregisters.go | 18 ++--- comid/test_vars.go | 4 ++ go.mod | 2 +- 7 files changed, 169 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 322b5d77..4d9b9133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ name: ci on: [push, pull_request] jobs: - # Test on various OS with default Go version. + # Test on various OS with specified Go version. tests: name: Test on ${{matrix.os}} runs-on: ${{ matrix.os }} @@ -12,6 +12,9 @@ jobs: matrix: os: [macos-latest, ubuntu-latest] steps: + - uses: actions/setup-go@v3 + with: + go-version: "1.19" - name: Checkout code uses: actions/checkout@v2 with: @@ -24,4 +27,4 @@ jobs: - name: Run tests run: | go version - make test + make test \ No newline at end of file diff --git a/comid/classid.go b/comid/classid.go index 4cf6f631..feb375e9 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -174,17 +174,44 @@ func (o *ClassID) SetUUID(uuid UUID) *ClassID { return o } -// SetOID sets the value of the targed ClassID to the supplied OID. +func (o ClassID) GetUUID() (UUID, error) { + switch t := o.Value.(type) { + case *TaggedUUID: + return UUID(*t), nil + case TaggedUUID: + return UUID(t), nil + default: + return UUID{}, fmt.Errorf("class-id type is: %T", t) + } +} + +// SetOID sets the value of the target ClassID to the supplied OID. // The OID is a string in dotted-decimal notation -func (o *ClassID) SetOID(s string) *ClassID { +func (o *ClassID) SetOID(s string) error { if o != nil { var berOID OID - if berOID.FromString(s) != nil { - return nil + if err := berOID.FromString(s); err != nil { + return err } o.Value = TaggedOID(berOID) } - return o + return nil +} + +// GetOID gets the value of the OID in a string dotted-decimal notation +func (o ClassID) GetOID() (string, error) { + switch t := o.Value.(type) { + case *TaggedOID: + oid := OID(*t) + stringOID := oid.String() + return stringOID, nil + case TaggedOID: + oid := OID(t) + stringOID := oid.String() + return stringOID, nil + default: + return "", fmt.Errorf("class-id type is: %T", t) + } } const ImplIDType = "psa.impl-id" diff --git a/comid/classid_test.go b/comid/classid_test.go index 28a9ce38..69277330 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -201,6 +201,92 @@ func TestClassID_SetOID_ok(t *testing.T) { } } +func TestClassID_SetOID_GetOID_ok(t *testing.T) { + tvs := []string{ + "1.2.3", + "1.2.3.4", + "1.2.3.4.5", + "1.2.3.4.5.6", + "1.2.3.4.5.6.7", + "1.2.3.4.5.6.7.8", + "1.2.3.4.5.6.7.8.9", + "1.2.3.4.5.6.7.8.9.10", + "1.2.3.4.5.6.7.8.9.10.11", + "1.2.3.4.5.6.7.8.9.10.11.12", + "1.2.3.4.5.6.7.8.9.10.11.12.13", + "1.2.3.4.5.6.7.8.9.10.11.12.13.14", + "1.2.3.4.5.6.7.8.9.10.11.12.13.14.15", + "1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16", + "1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17", + "1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18", + "1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19", + "1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20", + } + + for _, tv := range tvs { + c := &ClassID{} + err := c.SetOID(tv) + require.Nil(t, err) + out, err := c.GetOID() + require.NoError(t, err) + assert.Equal(t, tv, out) + } +} + +func TestClassID_SetOID_NOK(t *testing.T) { + tvs := []struct { + desc string + input string + expectedErr string + }{ + { + desc: "empyt OID", + input: "", + expectedErr: "empty OID", + }, + { + desc: "too little OID", + input: "1", + expectedErr: "invalid OID: got 1 arcs, expecting at least 3", + }, + { + desc: "still too little OID", + input: "1.2", + expectedErr: "invalid OID: got 2 arcs, expecting at least 3", + }, + { + desc: "negative arc", + input: "1.2.-3", + expectedErr: "invalid OID: negative arc -3 not allowed", + }, + { + desc: "not absolute", + input: ".1.2.3", + expectedErr: "OID must be absolute", + }, + { + desc: "not dotted decimal", + input: "iso(1) org(3) dod(6) iana(1)", + expectedErr: `invalid OID: strconv.Atoi: parsing "iso(1) org(3) dod(6) iana(1)": invalid syntax`, + }, + { + desc: "invalid format", + input: "1...", + expectedErr: `invalid OID: strconv.Atoi: parsing "": invalid syntax`, + }, + { + desc: "invalid format, no digits", + input: "1...", + expectedErr: `invalid OID: strconv.Atoi: parsing "": invalid syntax`, + }, + } + for _, tv := range tvs { + c := &ClassID{} + err := c.SetOID(tv.input) + assert.NotNil(t, err) + assert.EqualError(t, err, tv.expectedErr) + } +} func TestClassID_SetOID_bad(t *testing.T) { tvs := []string{ "", // empty @@ -220,6 +306,15 @@ func TestClassID_SetOID_bad(t *testing.T) { } } +func TestClassID_SetUUID_GetUUID_OK(t *testing.T) { + class := &ClassID{} + class = class.SetUUID(TestUUID) + require.NotNil(t, class) + uuid, err := class.GetUUID() + require.NoError(t, err) + assert.Equal(t, TestUUID, uuid) +} + func Test_NewImplIDClassID(t *testing.T) { classID, err := NewImplIDClassID(nil) expected := [32]byte{} diff --git a/comid/example_cca_realm_refval_test.go b/comid/example_cca_realm_refval_test.go index 1ef644ef..9de4e43b 100644 --- a/comid/example_cca_realm_refval_test.go +++ b/comid/example_cca_realm_refval_test.go @@ -26,6 +26,7 @@ func Example_cca_realm_refval() { // Vendor: Workload Client Ltd // ClassID: cd1f0e5526f9460db9d8f7fde171787c // InstanceID: 4284b5694ca6c0d2cf4789a0b95ac8025c818de52304364be7cd2981b2d2edc685b322277ec25819962413d8c9b2c1f5 + // RawValue: e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75 // Index: rim // Alg: sha-384 // Digest: 4284b5694ca6c0d2cf4789a0b95ac8025c818de52304364be7cd2981b2d2edc685b322277ec25819962413d8c9b2c1f5 @@ -84,8 +85,8 @@ func extractMeasurements(m Measurements) error { return fmt.Errorf("no measurements") } - for i, m := range m { - if err := extractMeasurement(m); err != nil { + for i, meas := range m { + if err := extractMeasurement(meas); err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } } @@ -94,6 +95,9 @@ func extractMeasurements(m Measurements) error { } func extractMeasurement(m Measurement) error { + if err := extractRealmPersonalizationValue(m.Val.RawValue); err != nil { + return fmt.Errorf("extracting realm personalization value: %w", err) + } if err := extractIntegrityRegisters(m.Val.IntegrityRegisters); err != nil { return fmt.Errorf("extracting digest: %w", err) } @@ -142,6 +146,21 @@ func extractRealmInstanceID(i *Instance) error { return nil } +func extractRealmPersonalizationValue(r *RawValue) error { + if r == nil { + return nil + } + b, err := r.GetBytes() + if err != nil { + return err + } + if len(b) != 64 { + return fmt.Errorf("invalid length %d, for realm personalization value", len(b)) + } + fmt.Printf("RawValue: %x\n", b) + return nil +} + func extractIntegrityRegisters(r *IntegrityRegisters) error { if r == nil { return fmt.Errorf("no integrity registers") @@ -153,7 +172,7 @@ func extractIntegrityRegisters(r *IntegrityRegisters) error { } for _, k := range keys { - d, ok := r.m[k] + d, ok := r.IndexMap[k] if !ok { return fmt.Errorf("unable to locate register index for: %s", k) } @@ -181,7 +200,7 @@ func extractRealmDigests(digests Digests) error { func extractRegisterIndexes(r *IntegrityRegisters) ([]string, error) { var keys [5]string - for k := range r.m { + for k := range r.IndexMap { switch t := k.(type) { case string: key := strings.ToLower(t) diff --git a/comid/integrityregisters.go b/comid/integrityregisters.go index e0aec76f..fb149305 100644 --- a/comid/integrityregisters.go +++ b/comid/integrityregisters.go @@ -18,11 +18,11 @@ type IRegisterIndex interface{} // IntegrityRegisters holds the Integrity Registers type IntegrityRegisters struct { - m map[IRegisterIndex]Digests + IndexMap map[IRegisterIndex]Digests } func NewIntegrityRegisters() *IntegrityRegisters { - return &IntegrityRegisters{m: make(map[IRegisterIndex]Digests)} + return &IntegrityRegisters{IndexMap: make(map[IRegisterIndex]Digests)} } // AddDigests allows inserting an array of digests at a specific index @@ -42,12 +42,12 @@ func (i *IntegrityRegisters) AddDigests(index IRegisterIndex, digests Digests) e // AddDigest allows inserting a digest at a specific index // Supported index types are uint and text func (i *IntegrityRegisters) AddDigest(index IRegisterIndex, digest swid.HashEntry) error { - if i.m == nil { + if i.IndexMap == nil { return fmt.Errorf("no register to add digest") } switch t := index.(type) { case string, uint, uint64: - i.m[t] = append(i.m[t], digest) + i.IndexMap[t] = append(i.IndexMap[t], digest) default: return fmt.Errorf("unexpected type for index: %T", t) } @@ -55,11 +55,11 @@ func (i *IntegrityRegisters) AddDigest(index IRegisterIndex, digest swid.HashEnt } func (i IntegrityRegisters) MarshalCBOR() ([]byte, error) { - return em.Marshal(i.m) + return em.Marshal(i.IndexMap) } func (i *IntegrityRegisters) UnmarshalCBOR(data []byte) error { - return dm.Unmarshal(data, &i.m) + return dm.Unmarshal(data, &i.IndexMap) } type keyTypeandVal struct { @@ -70,7 +70,7 @@ type keyTypeandVal struct { func (i IntegrityRegisters) MarshalJSON() ([]byte, error) { jmap := make(map[string]json.RawMessage) var newkey string - for key, val := range i.m { + for key, val := range i.IndexMap { var ktv keyTypeandVal switch t := key.(type) { case uint, uint64: @@ -98,8 +98,8 @@ func (i IntegrityRegisters) MarshalJSON() ([]byte, error) { } func (i *IntegrityRegisters) UnmarshalJSON(data []byte) error { - if i.m == nil { - i.m = make(map[IRegisterIndex]Digests) + if i.IndexMap == nil { + i.IndexMap = make(map[IRegisterIndex]Digests) } jmap := make(map[string]json.RawMessage) var index IRegisterIndex diff --git a/comid/test_vars.go b/comid/test_vars.go index 7c5969e7..f855fcd4 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -475,6 +475,10 @@ var ( "measurements": [ { "value": { + "raw-value": { + "type": "bytes", + "value": "5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82j/dOYjR6gk3stnqE5SJNdQ==" + }, "integrity-registers": { "rim": { "key-type": "text", diff --git a/go.mod b/go.mod index 96a5aa49..6533eb88 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/veraison/corim -go 1.18 +go 1.19 require ( github.com/fxamacker/cbor/v2 v2.5.0 From 4e7323cc532df22c86ffc460bc88da0f17b284f8 Mon Sep 17 00:00:00 2001 From: setrofim Date: Wed, 3 Jul 2024 17:13:54 +0100 Subject: [PATCH 039/110] fix: use value receivers for MarshalXXX methods (#117) Use value, rather than pointer, receivers for MarshalCBOR and MarshalJSON methods. Since those methods don't mutate the structures, pointers are not necessary, and using them prevents the methods from being invoked when marshaling an instance of that structure (rather than a pointer to one). This is not a huge deal as CoRIM fields typically use pointers, however it can lead to surprising results when playing with the structures on their own. Signed-off-by: Sergei Trofimov --- comid/classid.go | 4 ++-- comid/entity.go | 4 ++-- comid/flagsmap.go | 4 ++-- comid/measurement.go | 4 ++-- comid/triples.go | 4 ++-- corim/entity.go | 4 ++-- corim/meta.go | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/comid/classid.go b/comid/classid.go index feb375e9..d08dcde6 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -293,8 +293,8 @@ func (o TaggedImplID) Bytes() []byte { return o[:] } -func (o *TaggedImplID) MarshalJSON() ([]byte, error) { - return json.Marshal((*o)[:]) +func (o TaggedImplID) MarshalJSON() ([]byte, error) { + return json.Marshal((o)[:]) } func (o *TaggedImplID) UnmarshalJSON(data []byte) error { diff --git a/comid/entity.go b/comid/entity.go index 911c2111..c41c4f8d 100644 --- a/comid/entity.go +++ b/comid/entity.go @@ -90,7 +90,7 @@ func (o *Entity) UnmarshalCBOR(data []byte) error { } // MarshalCBOR serializes to CBOR -func (o *Entity) MarshalCBOR() ([]byte, error) { +func (o Entity) MarshalCBOR() ([]byte, error) { return encoding.SerializeStructToCBOR(em, o) } @@ -100,7 +100,7 @@ func (o *Entity) UnmarshalJSON(data []byte) error { } // MarshalJSON serializes to JSON -func (o *Entity) MarshalJSON() ([]byte, error) { +func (o Entity) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } diff --git a/comid/flagsmap.go b/comid/flagsmap.go index 94ed3e35..acbe3727 100644 --- a/comid/flagsmap.go +++ b/comid/flagsmap.go @@ -197,7 +197,7 @@ func (o *FlagsMap) UnmarshalCBOR(data []byte) error { } // MarshalCBOR serializes to CBOR -func (o *FlagsMap) MarshalCBOR() ([]byte, error) { +func (o FlagsMap) MarshalCBOR() ([]byte, error) { return encoding.SerializeStructToCBOR(em, o) } @@ -207,7 +207,7 @@ func (o *FlagsMap) UnmarshalJSON(data []byte) error { } // MarshalJSON serializes to JSON -func (o *FlagsMap) MarshalJSON() ([]byte, error) { +func (o FlagsMap) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } diff --git a/comid/measurement.go b/comid/measurement.go index 52fac579..c3150a08 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -374,7 +374,7 @@ func (o *Mval) UnmarshalCBOR(data []byte) error { } // MarshalCBOR serializes to CBOR -func (o *Mval) MarshalCBOR() ([]byte, error) { +func (o Mval) MarshalCBOR() ([]byte, error) { return encoding.SerializeStructToCBOR(em, o) } @@ -384,7 +384,7 @@ func (o *Mval) UnmarshalJSON(data []byte) error { } // MarshalJSON serializes to JSON -func (o *Mval) MarshalJSON() ([]byte, error) { +func (o Mval) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } diff --git a/comid/triples.go b/comid/triples.go index e3e9ac90..66164541 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -35,7 +35,7 @@ func (o *Triples) UnmarshalCBOR(data []byte) error { } // MarshalCBOR serializes to CBOR -func (o *Triples) MarshalCBOR() ([]byte, error) { +func (o Triples) MarshalCBOR() ([]byte, error) { return encoding.SerializeStructToCBOR(em, o) } @@ -45,7 +45,7 @@ func (o *Triples) UnmarshalJSON(data []byte) error { } // MarshalJSON serializes to JSON -func (o *Triples) MarshalJSON() ([]byte, error) { +func (o Triples) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } diff --git a/corim/entity.go b/corim/entity.go index 8a3169ac..f322ace1 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -103,7 +103,7 @@ func (o *Entity) UnmarshalCBOR(data []byte) error { } // MarshalCBOR serializes to CBOR -func (o *Entity) MarshalCBOR() ([]byte, error) { +func (o Entity) MarshalCBOR() ([]byte, error) { return encoding.SerializeStructToCBOR(em, o) } @@ -113,7 +113,7 @@ func (o *Entity) UnmarshalJSON(data []byte) error { } // MarshalJSON serializes to JSON -func (o *Entity) MarshalJSON() ([]byte, error) { +func (o Entity) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } diff --git a/corim/meta.go b/corim/meta.go index 9ce834e5..902c04c6 100644 --- a/corim/meta.go +++ b/corim/meta.go @@ -84,7 +84,7 @@ func (o *Signer) UnmarshalCBOR(data []byte) error { } // MarshalCBOR serializes to CBOR -func (o *Signer) MarshalCBOR() ([]byte, error) { +func (o Signer) MarshalCBOR() ([]byte, error) { return encoding.SerializeStructToCBOR(em, o) } @@ -94,7 +94,7 @@ func (o *Signer) UnmarshalJSON(data []byte) error { } // MarshalJSON serializes to JSON -func (o *Signer) MarshalJSON() ([]byte, error) { +func (o Signer) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } From 3e8dd65df689af550a510163e4f7687a04dfdd60 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Tue, 9 Jul 2024 13:10:38 +0100 Subject: [PATCH 040/110] chore: fix copyrights Add missing copyright headers and update copyright years in files that have been modified more recently than their copyright states. note: since updating the copyright is itself a modification, the update will always be to the current year. E.g. if a file was last modified in 2023, but its copyright is up to 2022; it will be update to the current year (2024), rather than the year of the previous modification. Signed-off-by: Sergei Trofimov --- cocli/cmd/comidCreate.go | 2 +- cocli/cmd/comidCreate_test.go | 2 +- cocli/cmd/comidDisplay.go | 2 +- cocli/cmd/comidValidate.go | 2 +- cocli/cmd/common.go | 2 +- cocli/cmd/corimCreate.go | 2 +- cocli/cmd/corimCreate_test.go | 2 +- cocli/cmd/corimDisplay.go | 2 +- cocli/cmd/corimDisplay_test.go | 2 +- cocli/cmd/corimExtract.go | 2 +- cocli/cmd/corimExtract_test.go | 2 +- cocli/cmd/corimSign.go | 2 +- cocli/cmd/corimSign_test.go | 2 +- cocli/cmd/corimVerify.go | 2 +- cocli/cmd/corimVerify_test.go | 2 +- cocli/cmd/cots.go | 2 +- cocli/cmd/cotsCreate.go | 2 +- cocli/cmd/cotsCreate_test.go | 2 +- cocli/cmd/cotsDisplay.go | 2 +- cocli/cmd/cotsDisplay_test.go | 2 +- cocli/cmd/root.go | 2 +- cocli/cmd/test_vars.go | 2 +- cocli/main.go | 2 +- comid/cbor.go | 2 +- comid/ccaplatformconfigid.go | 2 +- comid/ccaplatformconfigid_test.go | 2 +- comid/class.go | 2 +- comid/classid.go | 2 +- comid/classid_test.go | 2 +- comid/comid.go | 2 +- comid/comid_test.go | 2 ++ comid/cryptokey_test.go | 2 +- comid/digests_test.go | 2 +- comid/entity.go | 2 +- comid/entity_test.go | 2 +- comid/environment_test.go | 2 +- comid/example_cca_refval_test.go | 2 +- comid/example_psa_keys_test.go | 2 +- comid/example_psa_refval_test.go | 2 +- comid/extensions.go | 2 +- comid/extensions_test.go | 2 ++ comid/flagsmap.go | 2 +- comid/flagsmap_test.go | 2 ++ comid/group.go | 2 +- comid/group_test.go | 2 ++ comid/instance.go | 2 ++ comid/instance_test.go | 2 ++ comid/macaddr.go | 2 +- comid/measurement.go | 2 +- comid/measurement_test.go | 2 +- comid/oid.go | 2 +- comid/psareferencevalue.go | 2 +- comid/psareferencevalue_test.go | 2 +- comid/rawvalue.go | 2 +- comid/rawvalue_test.go | 2 ++ comid/referencevalue.go | 2 +- comid/referencevalue_test.go | 2 ++ comid/rel.go | 2 +- comid/rel_test.go | 2 +- comid/role.go | 2 +- comid/role_test.go | 2 +- comid/svn.go | 2 +- comid/svn_test.go | 2 ++ comid/test_vars.go | 2 +- comid/triples.go | 2 +- comid/ueid.go | 2 +- comid/ueid_test.go | 2 ++ comid/uuid.go | 2 +- comid/uuid_test.go | 2 ++ corim/cbor.go | 2 +- corim/entity.go | 2 +- corim/entity_test.go | 2 +- corim/extensions.go | 2 +- corim/extensions_test.go | 2 +- corim/meta.go | 2 +- corim/meta_test.go | 2 +- corim/role.go | 2 +- corim/role_test.go | 2 +- corim/signedcorim.go | 2 +- corim/signedcorim_test.go | 2 +- corim/signer.go | 2 ++ corim/unsignedcorim.go | 2 +- corim/unsignedcorim_test.go | 2 +- cots/abbreviated_swid_tag.go | 2 ++ cots/abbreviated_swid_test.go | 2 +- cots/cas_and_tas.go | 2 +- cots/cas_and_tas_test.go | 2 +- cots/cbor.go | 2 +- cots/cots.go | 2 +- cots/cots_test.go | 2 +- cots/eat_cwtclaims.go | 2 ++ cots/eat_cwtclaims_test.go | 2 ++ cots/env_group.go | 2 +- cots/env_group_test.go | 2 +- cots/example_abbreviated_swid_tag_test.go | 2 +- cots/example_test.go | 2 ++ cots/hardware_version_type.go | 2 ++ cots/test_vars.go | 2 +- encoding/cbor.go | 2 +- encoding/cbor_test.go | 2 +- encoding/embedded.go | 2 ++ encoding/json.go | 2 ++ encoding/json_test.go | 2 ++ extensions/extensions.go | 2 +- extensions/extensions_test.go | 2 +- extensions/typechoice.go | 2 ++ scripts/cov.py | 2 ++ scripts/licenses.sh | 2 ++ 108 files changed, 131 insertions(+), 85 deletions(-) diff --git a/cocli/cmd/comidCreate.go b/cocli/cmd/comidCreate.go index bc70c0cc..02b1a4d8 100644 --- a/cocli/cmd/comidCreate.go +++ b/cocli/cmd/comidCreate.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/comidCreate_test.go b/cocli/cmd/comidCreate_test.go index b651ebd6..9bac5dca 100644 --- a/cocli/cmd/comidCreate_test.go +++ b/cocli/cmd/comidCreate_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/comidDisplay.go b/cocli/cmd/comidDisplay.go index 8938c2bc..41683e66 100644 --- a/cocli/cmd/comidDisplay.go +++ b/cocli/cmd/comidDisplay.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/comidValidate.go b/cocli/cmd/comidValidate.go index 5fe7ff1f..0aef8767 100644 --- a/cocli/cmd/comidValidate.go +++ b/cocli/cmd/comidValidate.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/common.go b/cocli/cmd/common.go index 8ba508b0..adce111e 100644 --- a/cocli/cmd/common.go +++ b/cocli/cmd/common.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimCreate.go b/cocli/cmd/corimCreate.go index d4206464..c3bfd568 100644 --- a/cocli/cmd/corimCreate.go +++ b/cocli/cmd/corimCreate.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimCreate_test.go b/cocli/cmd/corimCreate_test.go index b2dfb770..3a68e672 100644 --- a/cocli/cmd/corimCreate_test.go +++ b/cocli/cmd/corimCreate_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimDisplay.go b/cocli/cmd/corimDisplay.go index dfc9dcf2..3b2b007a 100644 --- a/cocli/cmd/corimDisplay.go +++ b/cocli/cmd/corimDisplay.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimDisplay_test.go b/cocli/cmd/corimDisplay_test.go index 5e1ccb47..419fef6e 100644 --- a/cocli/cmd/corimDisplay_test.go +++ b/cocli/cmd/corimDisplay_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimExtract.go b/cocli/cmd/corimExtract.go index 80d39fd5..51968105 100644 --- a/cocli/cmd/corimExtract.go +++ b/cocli/cmd/corimExtract.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimExtract_test.go b/cocli/cmd/corimExtract_test.go index 8b476c98..34fabaf7 100644 --- a/cocli/cmd/corimExtract_test.go +++ b/cocli/cmd/corimExtract_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimSign.go b/cocli/cmd/corimSign.go index bf9bb8b2..74a0c98e 100644 --- a/cocli/cmd/corimSign.go +++ b/cocli/cmd/corimSign.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimSign_test.go b/cocli/cmd/corimSign_test.go index bc460376..dc04ea19 100644 --- a/cocli/cmd/corimSign_test.go +++ b/cocli/cmd/corimSign_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimVerify.go b/cocli/cmd/corimVerify.go index 6c5043d3..d0df48a7 100644 --- a/cocli/cmd/corimVerify.go +++ b/cocli/cmd/corimVerify.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/corimVerify_test.go b/cocli/cmd/corimVerify_test.go index 74de9996..f8e5b26d 100644 --- a/cocli/cmd/corimVerify_test.go +++ b/cocli/cmd/corimVerify_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/cots.go b/cocli/cmd/cots.go index adb1d78b..3e9090db 100644 --- a/cocli/cmd/cots.go +++ b/cocli/cmd/cots.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/cotsCreate.go b/cocli/cmd/cotsCreate.go index af44bca9..9c650a23 100644 --- a/cocli/cmd/cotsCreate.go +++ b/cocli/cmd/cotsCreate.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/cotsCreate_test.go b/cocli/cmd/cotsCreate_test.go index 8faa976d..56a5be8c 100644 --- a/cocli/cmd/cotsCreate_test.go +++ b/cocli/cmd/cotsCreate_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/cotsDisplay.go b/cocli/cmd/cotsDisplay.go index 5a198715..54254114 100644 --- a/cocli/cmd/cotsDisplay.go +++ b/cocli/cmd/cotsDisplay.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/cotsDisplay_test.go b/cocli/cmd/cotsDisplay_test.go index 11cfe234..8bcd06c5 100644 --- a/cocli/cmd/cotsDisplay_test.go +++ b/cocli/cmd/cotsDisplay_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/root.go b/cocli/cmd/root.go index efe8c153..d3b3268b 100644 --- a/cocli/cmd/root.go +++ b/cocli/cmd/root.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/cmd/test_vars.go b/cocli/cmd/test_vars.go index e4f05190..0a8cdad3 100644 --- a/cocli/cmd/test_vars.go +++ b/cocli/cmd/test_vars.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cmd diff --git a/cocli/main.go b/cocli/main.go index c3fc2a3f..b319cc3d 100644 --- a/cocli/main.go +++ b/cocli/main.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package main diff --git a/comid/cbor.go b/comid/cbor.go index b613e21b..f7f044f9 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/ccaplatformconfigid.go b/comid/ccaplatformconfigid.go index a55271ff..0272fc39 100644 --- a/comid/ccaplatformconfigid.go +++ b/comid/ccaplatformconfigid.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/ccaplatformconfigid_test.go b/comid/ccaplatformconfigid_test.go index 543390fd..6c272a85 100644 --- a/comid/ccaplatformconfigid_test.go +++ b/comid/ccaplatformconfigid_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/class.go b/comid/class.go index ab414740..f05b2496 100644 --- a/comid/class.go +++ b/comid/class.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/classid.go b/comid/classid.go index d08dcde6..e29d3e0c 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/classid_test.go b/comid/classid_test.go index 69277330..d5726ec7 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/comid.go b/comid/comid.go index 5052cc0c..b22430fe 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/comid_test.go b/comid/comid_test.go index a8cdf198..98fc3c32 100644 --- a/comid/comid_test.go +++ b/comid/comid_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index 8a53edeb..05022af7 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/digests_test.go b/comid/digests_test.go index 141bb90e..ff14b359 100644 --- a/comid/digests_test.go +++ b/comid/digests_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/entity.go b/comid/entity.go index c41c4f8d..b53b1a1b 100644 --- a/comid/entity.go +++ b/comid/entity.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/entity_test.go b/comid/entity_test.go index d634a2f4..b41c48e4 100644 --- a/comid/entity_test.go +++ b/comid/entity_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/environment_test.go b/comid/environment_test.go index f33d1ebf..1d2abb40 100644 --- a/comid/environment_test.go +++ b/comid/environment_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/example_cca_refval_test.go b/comid/example_cca_refval_test.go index 9b52304c..51bb5a21 100644 --- a/comid/example_cca_refval_test.go +++ b/comid/example_cca_refval_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/example_psa_keys_test.go b/comid/example_psa_keys_test.go index 3780cbbd..6b2d8d5a 100644 --- a/comid/example_psa_keys_test.go +++ b/comid/example_psa_keys_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/example_psa_refval_test.go b/comid/example_psa_refval_test.go index 18817d91..c4b12cd6 100644 --- a/comid/example_psa_refval_test.go +++ b/comid/example_psa_refval_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/extensions.go b/comid/extensions.go index df9d0d2f..fa90f43c 100644 --- a/comid/extensions.go +++ b/comid/extensions.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/extensions_test.go b/comid/extensions_test.go index 2d96f709..01897574 100644 --- a/comid/extensions_test.go +++ b/comid/extensions_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/flagsmap.go b/comid/flagsmap.go index acbe3727..f7aa1001 100644 --- a/comid/flagsmap.go +++ b/comid/flagsmap.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/flagsmap_test.go b/comid/flagsmap_test.go index e9bc477c..665552b1 100644 --- a/comid/flagsmap_test.go +++ b/comid/flagsmap_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/group.go b/comid/group.go index c909a36f..84908e58 100644 --- a/comid/group.go +++ b/comid/group.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/group_test.go b/comid/group_test.go index e415bccc..cdc571fc 100644 --- a/comid/group_test.go +++ b/comid/group_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/instance.go b/comid/instance.go index b22f5ae1..f48d88d3 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/instance_test.go b/comid/instance_test.go index 37cafcbd..744c0f8c 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/macaddr.go b/comid/macaddr.go index e722b84a..8733c4f5 100644 --- a/comid/macaddr.go +++ b/comid/macaddr.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/measurement.go b/comid/measurement.go index c3150a08..f6ee8c27 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/measurement_test.go b/comid/measurement_test.go index fe87223a..77a1335e 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/oid.go b/comid/oid.go index 24d366e6..a080b1fd 100644 --- a/comid/oid.go +++ b/comid/oid.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/psareferencevalue.go b/comid/psareferencevalue.go index 0ffacf97..ff98c85a 100644 --- a/comid/psareferencevalue.go +++ b/comid/psareferencevalue.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/psareferencevalue_test.go b/comid/psareferencevalue_test.go index 72116cc2..0207becd 100644 --- a/comid/psareferencevalue_test.go +++ b/comid/psareferencevalue_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/rawvalue.go b/comid/rawvalue.go index 5f7554c3..fd3ceae9 100644 --- a/comid/rawvalue.go +++ b/comid/rawvalue.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/rawvalue_test.go b/comid/rawvalue_test.go index c5ac0e04..d28d6aad 100644 --- a/comid/rawvalue_test.go +++ b/comid/rawvalue_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/referencevalue.go b/comid/referencevalue.go index ece16d98..a848d682 100644 --- a/comid/referencevalue.go +++ b/comid/referencevalue.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/referencevalue_test.go b/comid/referencevalue_test.go index f999c876..6fe431f3 100644 --- a/comid/referencevalue_test.go +++ b/comid/referencevalue_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/rel.go b/comid/rel.go index 39e40d64..ce18997b 100644 --- a/comid/rel.go +++ b/comid/rel.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/rel_test.go b/comid/rel_test.go index 86f6d748..7e05235b 100644 --- a/comid/rel_test.go +++ b/comid/rel_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/role.go b/comid/role.go index 3cd3d0a4..131c24ec 100644 --- a/comid/role.go +++ b/comid/role.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/role_test.go b/comid/role_test.go index 103f4085..c4a6b5e0 100644 --- a/comid/role_test.go +++ b/comid/role_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/svn.go b/comid/svn.go index 29fea097..6d634a2f 100644 --- a/comid/svn.go +++ b/comid/svn.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/svn_test.go b/comid/svn_test.go index 0ffb8f28..7a4841fb 100644 --- a/comid/svn_test.go +++ b/comid/svn_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/test_vars.go b/comid/test_vars.go index f855fcd4..ce3b59fa 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -1,4 +1,4 @@ -// Copyright 2021-2023 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/triples.go b/comid/triples.go index 66164541..7aec9dff 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/ueid.go b/comid/ueid.go index cf64ffa2..43e060ed 100644 --- a/comid/ueid.go +++ b/comid/ueid.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/ueid_test.go b/comid/ueid_test.go index a119ad44..81f39e07 100644 --- a/comid/ueid_test.go +++ b/comid/ueid_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/comid/uuid.go b/comid/uuid.go index ba439a0f..2c7b09fc 100644 --- a/comid/uuid.go +++ b/comid/uuid.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/uuid_test.go b/comid/uuid_test.go index 5308854a..d2dc4f1e 100644 --- a/comid/uuid_test.go +++ b/comid/uuid_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package comid import ( diff --git a/corim/cbor.go b/corim/cbor.go index 17464d1d..846759ce 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/entity.go b/corim/entity.go index f322ace1..675dcd81 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/entity_test.go b/corim/entity_test.go index aec1be92..cd7bcc3e 100644 --- a/corim/entity_test.go +++ b/corim/entity_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/extensions.go b/corim/extensions.go index 0bd3502f..f7e21546 100644 --- a/corim/extensions.go +++ b/corim/extensions.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/extensions_test.go b/corim/extensions_test.go index 043d9a52..617b7bd0 100644 --- a/corim/extensions_test.go +++ b/corim/extensions_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/meta.go b/corim/meta.go index 902c04c6..f83730e2 100644 --- a/corim/meta.go +++ b/corim/meta.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/meta_test.go b/corim/meta_test.go index 811495cf..8d04f017 100644 --- a/corim/meta_test.go +++ b/corim/meta_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/role.go b/corim/role.go index 7ef276f7..07f55a2f 100644 --- a/corim/role.go +++ b/corim/role.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/role_test.go b/corim/role_test.go index 54b4433c..de11a7ab 100644 --- a/corim/role_test.go +++ b/corim/role_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 755462e1..2a790420 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index eae15d45..9cfc4cf2 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/signer.go b/corim/signer.go index 04182d60..90993888 100644 --- a/corim/signer.go +++ b/corim/signer.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package corim import ( diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 1247527e..04addbe2 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 918dde0f..7374d05a 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim diff --git a/cots/abbreviated_swid_tag.go b/cots/abbreviated_swid_tag.go index 65060046..00eeb0c6 100644 --- a/cots/abbreviated_swid_tag.go +++ b/cots/abbreviated_swid_tag.go @@ -1,3 +1,5 @@ +// Copyright 2023-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package cots import ( diff --git a/cots/abbreviated_swid_test.go b/cots/abbreviated_swid_test.go index d069c2ea..545a23bc 100644 --- a/cots/abbreviated_swid_test.go +++ b/cots/abbreviated_swid_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Contributors to the Veraison project. +// Copyright 2020-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 // The tests in this file were copied from softwareidentity_test.go in github.com/veraison/swid with minor edits made diff --git a/cots/cas_and_tas.go b/cots/cas_and_tas.go index 728bafb6..b2125f92 100644 --- a/cots/cas_and_tas.go +++ b/cots/cas_and_tas.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/cots/cas_and_tas_test.go b/cots/cas_and_tas_test.go index f0116e97..46c8ad1e 100644 --- a/cots/cas_and_tas_test.go +++ b/cots/cas_and_tas_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/cots/cbor.go b/cots/cbor.go index 6d69ecd1..94b5e8ea 100644 --- a/cots/cbor.go +++ b/cots/cbor.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/cots/cots.go b/cots/cots.go index 477ac88a..412bc184 100644 --- a/cots/cots.go +++ b/cots/cots.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/cots/cots_test.go b/cots/cots_test.go index 134f1d7d..d3e4c23b 100644 --- a/cots/cots_test.go +++ b/cots/cots_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/cots/eat_cwtclaims.go b/cots/eat_cwtclaims.go index 19d2aead..518193c3 100644 --- a/cots/eat_cwtclaims.go +++ b/cots/eat_cwtclaims.go @@ -1,3 +1,5 @@ +// Copyright 2023-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package cots import ( diff --git a/cots/eat_cwtclaims_test.go b/cots/eat_cwtclaims_test.go index df02315e..6b4b6fcc 100644 --- a/cots/eat_cwtclaims_test.go +++ b/cots/eat_cwtclaims_test.go @@ -1,3 +1,5 @@ +// Copyright 2023-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package cots import ( diff --git a/cots/env_group.go b/cots/env_group.go index 01d98efb..795dd834 100644 --- a/cots/env_group.go +++ b/cots/env_group.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/cots/env_group_test.go b/cots/env_group_test.go index 5cd41090..a1dcbc3e 100644 --- a/cots/env_group_test.go +++ b/cots/env_group_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/cots/example_abbreviated_swid_tag_test.go b/cots/example_abbreviated_swid_tag_test.go index a4ca3cb4..2f623779 100644 --- a/cots/example_abbreviated_swid_tag_test.go +++ b/cots/example_abbreviated_swid_tag_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Contributors to the Veraison project. +// Copyright 2020-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/cots/example_test.go b/cots/example_test.go index f94ecbd7..589f79c4 100644 --- a/cots/example_test.go +++ b/cots/example_test.go @@ -1,3 +1,5 @@ +// Copyright 2023-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package cots import ( diff --git a/cots/hardware_version_type.go b/cots/hardware_version_type.go index b93e071b..f7f2451e 100644 --- a/cots/hardware_version_type.go +++ b/cots/hardware_version_type.go @@ -1,3 +1,5 @@ +// Copyright 2023-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package cots import "github.com/veraison/swid" diff --git a/cots/test_vars.go b/cots/test_vars.go index ceb42465..5081933d 100644 --- a/cots/test_vars.go +++ b/cots/test_vars.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots diff --git a/encoding/cbor.go b/encoding/cbor.go index 6ef87425..380442ae 100644 --- a/encoding/cbor.go +++ b/encoding/cbor.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package encoding diff --git a/encoding/cbor_test.go b/encoding/cbor_test.go index 1dda9146..b509e0ab 100644 --- a/encoding/cbor_test.go +++ b/encoding/cbor_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package encoding diff --git a/encoding/embedded.go b/encoding/embedded.go index 1bfeb999..8f758cf3 100644 --- a/encoding/embedded.go +++ b/encoding/embedded.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package encoding import "reflect" diff --git a/encoding/json.go b/encoding/json.go index f9b24f89..3cca39c0 100644 --- a/encoding/json.go +++ b/encoding/json.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package encoding import ( diff --git a/encoding/json_test.go b/encoding/json_test.go index 85eca9ff..d25e0287 100644 --- a/encoding/json_test.go +++ b/encoding/json_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package encoding import ( diff --git a/extensions/extensions.go b/extensions/extensions.go index 4be60fd1..3362752f 100644 --- a/extensions/extensions.go +++ b/extensions/extensions.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package extensions diff --git a/extensions/extensions_test.go b/extensions/extensions_test.go index 74d44f52..901bbe6a 100644 --- a/extensions/extensions_test.go +++ b/extensions/extensions_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package extensions diff --git a/extensions/typechoice.go b/extensions/typechoice.go index 5510c42a..fa0e91a6 100644 --- a/extensions/typechoice.go +++ b/extensions/typechoice.go @@ -1,3 +1,5 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 package extensions import ( diff --git a/scripts/cov.py b/scripts/cov.py index 6d349d70..8b19fabe 100644 --- a/scripts/cov.py +++ b/scripts/cov.py @@ -1,3 +1,5 @@ +# Copyright 2024 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 import os import re import sys diff --git a/scripts/licenses.sh b/scripts/licenses.sh index f5d8858f..accdad92 100755 --- a/scripts/licenses.sh +++ b/scripts/licenses.sh @@ -1,4 +1,6 @@ #!/bin/bash +# Copyright 2024 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 set -e From 3c6fbdd20ad8a8d0538a66419aa19fe5aeeb24f9 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Tue, 9 Jul 2024 13:09:10 +0100 Subject: [PATCH 041/110] fix!: fix map extensions Fix map extensions for structs hidden behind slices by adding custom container type and a downward propagation mechanism. Up to this point, map extensions could only be registered with the structure which they are extending. E.g., if I want to add field to Mval, I create a new struct, MyMvalExtensions, defining the new fields, and then register an instance of it with an Mval instance. I can then unmarshal serialized data into it, and if that data contains extended fields, the registered extension struct will get populated. If the struct is a field in another struct (e.g. Mval is part of Measurement), and I want to unmarshal the outer struct, I have to instantiate both structs and register my extensions. E.g. I create a Measurement instance, populate its Val field with a new Mval instance, register my extensions with the Mval instance, and then I can unmarshal. There is a problem, however, when the extended struct is behind a slice. E.g. a reference value can contain multiple measurements (inside a slice field). If I need to unmarshal a reference value, I cannot pre-initialize Mvals to register my extension, as I don't know how many I will need. To solve this, add a new container type, extensions.Collection, that will be used instead of slices. This will allow registering extensions, and will cache them internally. It will then be the Collection's responsibility to register extensions during unmarshaling. There may be multiple levels of collections. For example, a Triples structure can contain multiple reference values, each of which can contain multiple measurements. If I want to unmarshal a Triples instance, I will need to propagate my Mval extensions all the way down to the Measurements' Mvals. Additionally, there may be other structs in the hierarchy that also allow extensions (e.g. the Triples struct itself does). To solve this, add a propagation mechanism that allows routing extensions to the correct point in the hierarchy. RegisterExtensions has been modified to accept an extensions.Map, rather than an extensions.IExtensionsValue. This is a mapping of extension.Point's (strings) to extensions.IMapValue's (IExtensionsValue rename). Each extensible type (one that embeds Extensions) defines a corresponding extensions.Point. A type is aware of its own extension point, and also those of its fields and can use this to route IMapValue instances appropriately. RegisterExtensions now also returns an error, which may happen if an unknown Point is provided. A number of issues surfaced as part of this work, and so additional fixes/refactors have also been implemented: - Fix bug where empty reference values arrays were allowed - Fix bug in NewMkey where arg was not being used to initialize the new Mkey - Fix a whole bunch of linter issues - Update Go to 1.22 -- fixes security issues - Fix handling of "-" in json/cbor tags when (un)marshaling fields - Move Signer from meta.go to signer.go; despite signer.go already existing (containing some COSE-related utility functions), Singer was inside meta.go - Fix Meta error reporting: report the field as part of the error - Add ToJSON() to UnsignedCorim; this functionality is not necessary internally, but may be useful to code using the library. - Shore up coverage BREAKING CHANGE: RegisterExtensions() has been changed to accept an extensions.Map rather than an extensions.IExtensionsValue, and to return an error. BREAKING CHANGE: A number of slice fields have been changed to use the generic extensions.Collection (usually behind a non-generic alias). BREAKING CHANGE: IExtensionsValue has been renamed to IMapValue, as it is already part of the extensions sub-package and having "extensions" in its name is redundant (this aligns with Go best practices). The new name draws better parallel with the other extensions value type, ITypeChoiceValue (representing map and type-choice extensions respectively). BREAKING CHANGE: ReferenceValue and EndorsedValue types have been replaced with the single ValueTriple type, as the two types were duplicates of each other. Signed-off-by: Sergei Trofimov --- .github/workflows/ci-go-cover.yml | 2 +- .github/workflows/ci.yml | 4 +- .github/workflows/linters.yml | 4 +- cocli/cmd/corimDisplay.go | 6 +- cocli/cmd/corimExtract.go | 6 +- cocli/cmd/corimSign_test.go | 2 +- comid/classid_test.go | 15 +- comid/comid.go | 70 ++++-- comid/comid_test.go | 49 +++- comid/cryptokey_test.go | 2 +- comid/endorsedvalue.go | 28 --- comid/endorsedvalue_test.go | 23 -- comid/entity.go | 47 ++-- comid/entity_test.go | 12 +- comid/example_cca_realm_refval_test.go | 8 +- comid/example_cca_refval_test.go | 6 +- comid/example_psa_refval_test.go | 8 +- comid/example_test.go | 16 +- comid/extensions.go | 32 ++- comid/extensions_test.go | 10 +- comid/flagsmap.go | 31 ++- comid/group_test.go | 4 +- comid/instance_test.go | 2 +- comid/measurement.go | 105 +++++++- comid/measurement_test.go | 26 +- comid/measurements.go | 38 --- comid/oid.go | 12 +- comid/referencevalue.go | 24 -- comid/svn_test.go | 4 +- comid/triples.go | 118 +++++++-- comid/triples_test.go | 86 +++++++ comid/valuetriple.go | 91 +++++++ ...rencevalue_test.go => valuetriple_test.go} | 2 +- corim/entity.go | 72 ++++-- corim/entity_test.go | 8 +- corim/extensions.go | 12 +- corim/extensions_test.go | 16 +- corim/meta.go | 104 ++------ corim/meta_test.go | 35 ++- corim/signedcorim.go | 19 ++ corim/signedcorim_test.go | 15 ++ corim/signer.go | 97 ++++++++ corim/signer_test.go | 61 +++++ corim/unsignedcorim.go | 57 ++++- corim/unsignedcorim_test.go | 122 +++++++++- encoding/cbor.go | 8 + encoding/cbor_test.go | 5 +- encoding/json.go | 8 + encoding/json_test.go | 5 +- extensions/README.md | 76 ++++-- extensions/cbor.go | 38 +++ extensions/collection.go | 225 ++++++++++++++++++ extensions/collection_test.go | 170 +++++++++++++ extensions/corim-map-extensions.png | Bin 0 -> 249791 bytes extensions/extensions.go | 57 ++++- extensions/extensions_test.go | 31 ++- extensions/typechoice_test.go | 29 +++ go.mod | 2 +- 58 files changed, 1711 insertions(+), 454 deletions(-) delete mode 100644 comid/endorsedvalue.go delete mode 100644 comid/endorsedvalue_test.go delete mode 100644 comid/measurements.go delete mode 100644 comid/referencevalue.go create mode 100644 comid/triples_test.go create mode 100644 comid/valuetriple.go rename comid/{referencevalue_test.go => valuetriple_test.go} (96%) create mode 100644 corim/signer_test.go create mode 100644 extensions/cbor.go create mode 100644 extensions/collection.go create mode 100644 extensions/collection_test.go create mode 100644 extensions/corim-map-extensions.png create mode 100644 extensions/typechoice_test.go diff --git a/.github/workflows/ci-go-cover.yml b/.github/workflows/ci-go-cover.yml index 9dce92ca..7d391e88 100644 --- a/.github/workflows/ci-go-cover.yml +++ b/.github/workflows/ci-go-cover.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: "1.19" + go-version: "1.22" - name: Checkout code uses: actions/checkout@v2 - name: Install mockgen diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d9b9133..022afead 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: "1.19" + go-version: "1.22" - name: Checkout code uses: actions/checkout@v2 with: @@ -27,4 +27,4 @@ jobs: - name: Run tests run: | go version - make test \ No newline at end of file + make test diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 92368c61..f5140a8c 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -10,13 +10,13 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: "1.19" + go-version: "1.22" - name: Checkout code uses: actions/checkout@v2 - name: Install golangci-lint run: | go version - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2 - name: Install mockgen run: | go install github.com/golang/mock/mockgen@v1.5.0 diff --git a/cocli/cmd/corimDisplay.go b/cocli/cmd/corimDisplay.go index 3b2b007a..982b6450 100644 --- a/cocli/cmd/corimDisplay.go +++ b/cocli/cmd/corimDisplay.go @@ -43,11 +43,7 @@ func NewCorimDisplayCmd() *cobra.Command { return err } - if err := display(*corimDisplayCorimFile, *corimDisplayShowTags); err != nil { - return err - } - - return nil + return display(*corimDisplayCorimFile, *corimDisplayShowTags) }, } diff --git a/cocli/cmd/corimExtract.go b/cocli/cmd/corimExtract.go index 51968105..3761ee04 100644 --- a/cocli/cmd/corimExtract.go +++ b/cocli/cmd/corimExtract.go @@ -45,11 +45,7 @@ func NewCorimExtractCmd() *cobra.Command { return err } - if err := extract(*corimExtractCorimFile, corimExtractOutputDir); err != nil { - return err - } - - return nil + return extract(*corimExtractCorimFile, corimExtractOutputDir) }, } diff --git a/cocli/cmd/corimSign_test.go b/cocli/cmd/corimSign_test.go index dc04ea19..f8f78ecc 100644 --- a/cocli/cmd/corimSign_test.go +++ b/cocli/cmd/corimSign_test.go @@ -167,7 +167,7 @@ func Test_CorimSignCmd_invalid_meta_file(t *testing.T) { require.NoError(t, err) err = cmd.Execute() - assert.EqualError(t, err, "error validating CoRIM Meta: invalid meta: empty name") + assert.EqualError(t, err, "error validating CoRIM Meta: invalid signer: empty name") } func Test_CorimSignCmd_non_existent_key_file(t *testing.T) { diff --git a/comid/classid_test.go b/comid/classid_test.go index d5726ec7..a64b2b9c 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -468,7 +468,7 @@ func Test_TaggedInt(t *testing.T) { type testClassID [4]byte -func newTestClassID(val any) (*ClassID, error) { +func newTestClassID(_ any) (*ClassID, error) { return &ClassID{&testClassID{0x74, 0x65, 0x73, 0x74}}, nil } @@ -658,3 +658,16 @@ func TestClassID_UnmarshalJSON_Bytes_NOK(t *testing.T) { }) } } + +func TestClassID_ImplID(t *testing.T) { + var implID ImplID + copy(implID[:], MustHexDecode(t, + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")) + + var classID ClassID + + classID.SetImplID(implID) + other, err := classID.GetImplID() + assert.NoError(t, err) + assert.Equal(t, implID, other) +} diff --git a/comid/comid.go b/comid/comid.go index b22430fe..7b075a5f 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -31,13 +31,33 @@ func NewComid() *Comid { } // RegisterExtensions registers a struct as a collections of extensions -func (o *Comid) RegisterExtensions(exts extensions.IExtensionsValue) { - o.Extensions.Register(exts) +func (o *Comid) RegisterExtensions(exts extensions.Map) error { + triplesExts := extensions.NewMap() + + for p, v := range exts { + switch p { + case ExtComid: + o.Extensions.Register(v) + case ExtEntity: + if o.Entities == nil { + o.Entities = NewEntities() + } + + entMap := extensions.NewMap().Add(ExtEntity, v) + if err := o.Entities.RegisterExtensions(entMap); err != nil { + return err + } + default: + triplesExts.Add(p, v) + } + } + + return o.Triples.RegisterExtensions(triplesExts) } -// GetExtensions returns pervisouosly registered extension -func (o *Comid) GetExtensions() extensions.IExtensionsValue { - return o.Extensions.IExtensionsValue +// GetExtensions returns previously registered extension +func (o *Comid) GetExtensions() extensions.IMapValue { + return o.Extensions.IMapValue } // SetLanguage sets the language used in the target Comid to the supplied @@ -129,7 +149,7 @@ func (o *Comid) AddEntity(name string, regID *string, roles ...Role) *Comid { o.Entities = new(Entities) } - if o.Entities.AddEntity(e) == nil { + if o.Entities.Add(&e) == nil { return nil } } @@ -164,10 +184,10 @@ func (o *Comid) AddLinkedTag(tagID interface{}, rel Rel) *Comid { // AddReferenceValue adds the supplied reference value to the // reference-triples list of the target Comid. -func (o *Comid) AddReferenceValue(val ReferenceValue) *Comid { +func (o *Comid) AddReferenceValue(val ValueTriple) *Comid { if o != nil { if o.Triples.ReferenceValues == nil { - o.Triples.ReferenceValues = new([]ReferenceValue) + o.Triples.ReferenceValues = NewValueTriples() } if o.Triples.AddReferenceValue(val) == nil { @@ -179,10 +199,10 @@ func (o *Comid) AddReferenceValue(val ReferenceValue) *Comid { // AddEndorsedValue adds the supplied endorsed value to the // endorsed-triples list of the target Comid. -func (o *Comid) AddEndorsedValue(val EndorsedValue) *Comid { +func (o *Comid) AddEndorsedValue(val ValueTriple) *Comid { if o != nil { if o.Triples.EndorsedValues == nil { - o.Triples.EndorsedValues = new([]EndorsedValue) + o.Triples.EndorsedValues = NewValueTriples() } if o.Triples.AddEndorsedValue(val) == nil { @@ -252,6 +272,16 @@ func (o Comid) ToCBOR() ([]byte, error) { return nil, err } + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.Entities != nil && o.Entities.IsEmpty() { + o.Entities = nil + } + return encoding.SerializeStructToCBOR(em, &o) } @@ -260,20 +290,30 @@ func (o *Comid) FromCBOR(data []byte) error { return encoding.PopulateStructFromCBOR(dm, data, o) } -// FromJSON deserializes a JSON-encoded CoMID into the target Comid -func (o *Comid) FromJSON(data []byte) error { - return encoding.PopulateStructFromJSON(data, o) -} - // ToJSON serializes the target Comid to JSON func (o Comid) ToJSON() ([]byte, error) { if err := o.Valid(); err != nil { return nil, err } + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.Entities != nil && o.Entities.IsEmpty() { + o.Entities = nil + } + return encoding.SerializeStructToJSON(&o) } +// FromJSON deserializes a JSON-encoded CoMID into the target Comid +func (o *Comid) FromJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + func (o Comid) ToJSONPretty(indent string) ([]byte, error) { if err := o.Valid(); err != nil { return nil, err diff --git a/comid/comid_test.go b/comid/comid_test.go index 98fc3c32..7aed60dd 100644 --- a/comid/comid_test.go +++ b/comid/comid_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/veraison/corim/extensions" "github.com/veraison/swid" ) @@ -22,7 +23,13 @@ func Test_Comid_Extensions(t *testing.T) { FieldOne string `cbor:"-1,keyasint" json:"field-one"` } - c.RegisterExtensions(&ComidExt{}) + extMap := extensions.NewMap(). + Add(ExtComid, &ComidExt{}). + Add(ExtEntity, &struct{}{}). + Add(ExtReferenceValue, &struct{}{}). + Add(ExtEndorsedValueFlags, &struct{}{}) + err = c.RegisterExtensions(extMap) + require.NoError(t, err) err = c.Set("field-one", "foo") assert.NoError(t, err) @@ -36,7 +43,18 @@ func Test_Comid_ToJSONPretty(t *testing.T) { assert.EqualError(t, err, "tag-identity validation failed: empty tag-id") c.TagIdentity = TagIdentity{TagID: *swid.NewTagID("test"), TagVersion: 1} - c.Triples = Triples{ReferenceValues: &[]ReferenceValue{}} + c.Triples = Triples{ + ReferenceValues: NewValueTriples().Add(&ValueTriple{ + Environment: Environment{ + Instance: MustNewUUIDInstance(TestUUID), + }, + Measurements: *NewMeasurements().Add(&Measurement{ + Val: Mval{ + RawValue: NewRawValue().SetBytes(MustHexDecode(t, "deadbeef")), + }, + }), + }), + } expected := `{ "tag-identity": { @@ -44,10 +62,35 @@ func Test_Comid_ToJSONPretty(t *testing.T) { "version": 1 }, "triples": { - "reference-values": [] + "reference-values": [ + { + "environment": { + "instance": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + } + }, + "measurements": [ + { + "value": { + "raw-value": { + "type": "bytes", + "value": "3q2+7w==" + } + } + } + ] + } + ] } }` v, err := c.ToJSONPretty(" ") require.NoError(t, err) assert.Equal(t, expected, string(v)) } + +func Test_String2URI_nok(t *testing.T) { + s := "@@@" + _, err := String2URI(&s) + assert.EqualError(t, err, `expecting an absolute URI: "@@@" is not an absolute URI`) +} diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index 05022af7..d898d48e 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -403,7 +403,7 @@ func Test_NewCryptoKey_negative(t *testing.T) { type testCryptoKey [4]byte -func newTestCryptoKey(val any) (*CryptoKey, error) { +func newTestCryptoKey(_ any) (*CryptoKey, error) { return &CryptoKey{&testCryptoKey{0x74, 0x64, 0x73, 0x74}}, nil } diff --git a/comid/endorsedvalue.go b/comid/endorsedvalue.go deleted file mode 100644 index a907a3dc..00000000 --- a/comid/endorsedvalue.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import "fmt" - -// EndorsedValue stores an endorsed-triple-record with CBOR and JSON -// serializations. Note that the CBOR serialization packs the structure into an -// array. Instead, when serializing to JSON, the structure is converted into an -// object. -type EndorsedValue struct { - _ struct{} `cbor:",toarray"` - Environment Environment `json:"environment"` - Measurements Measurements `json:"measurements"` -} - -func (o EndorsedValue) Valid() error { - if err := o.Environment.Valid(); err != nil { - return fmt.Errorf("environment validation failed: %w", err) - } - - if err := o.Measurements.Valid(); err != nil { - return fmt.Errorf("measurements validation failed: %w", err) - } - - return nil -} diff --git a/comid/endorsedvalue_test.go b/comid/endorsedvalue_test.go deleted file mode 100644 index f9b2e1ac..00000000 --- a/comid/endorsedvalue_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package comid - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestEndorsedValue_Valid_empty_environment(t *testing.T) { - tv := EndorsedValue{} - - assert.EqualError(t, tv.Valid(), "environment validation failed: environment must not be empty") -} - -func TestEndorsedValue_Valid_empty_measurements(t *testing.T) { - tv := EndorsedValue{ - Environment: Environment{ - Class: NewClassUUID(TestUUID), - }, - } - - assert.EqualError(t, tv.Valid(), "measurements validation failed: no measurement entries") -} diff --git a/comid/entity.go b/comid/entity.go index b53b1a1b..72902578 100644 --- a/comid/entity.go +++ b/comid/entity.go @@ -23,13 +23,22 @@ type Entity struct { } // RegisterExtensions registers a struct as a collections of extensions -func (o *Entity) RegisterExtensions(exts extensions.IExtensionsValue) { - o.Extensions.Register(exts) +func (o *Entity) RegisterExtensions(exts extensions.Map) error { + for p, v := range exts { + switch p { + case ExtEntity: + o.Extensions.Register(v) + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + + return nil } -// GetExtensions returns pervisouosly registered extension -func (o *Entity) GetExtensions() extensions.IExtensionsValue { - return o.Extensions.IExtensionsValue +// GetExtensions returns previously registered extension +func (o *Entity) GetExtensions() extensions.IMapValue { + return o.Extensions.IMapValue } // SetEntityName is used to set the EntityName field of Entity using supplied name @@ -104,30 +113,14 @@ func (o Entity) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } -// Entities is an array of entity-map's -type Entities []Entity - -// NewEntities instantiates an empty entity-map array -func NewEntities() *Entities { - return new(Entities) +// Entities is a container for Entity instances and their extensions. +// It is a thin wrapper around extensions.Collection. +type Entities struct { + extensions.Collection[Entity, *Entity] } -// AddEntity adds the supplied entity-map to the target Entities -func (o *Entities) AddEntity(e Entity) *Entities { - if o != nil { - *o = append(*o, e) - } - return o -} - -// Valid iterates over the range of individual entities to check for validity -func (o Entities) Valid() error { - for i, m := range o { - if err := m.Valid(); err != nil { - return fmt.Errorf("entity at index %d: %w", i, err) - } - } - return nil +func NewEntities() *Entities { + return &Entities{*extensions.NewCollection[Entity]()} } // EntityName encapsulates the name of the associated Entity. The CoRIM diff --git a/comid/entity_test.go b/comid/entity_test.go index b41c48e4..10ff2e13 100644 --- a/comid/entity_test.go +++ b/comid/entity_test.go @@ -50,16 +50,16 @@ func TestEntity_Valid_name_regid_and_roles(t *testing.T) { } func TestEntities_Valid_empty(t *testing.T) { - e := Entity{} - tv := NewEntities().AddEntity(e) + e := &Entity{} + tv := NewEntities().Add(e) require.NotNil(t, tv) err := tv.Valid() - assert.EqualError(t, err, "entity at index 0: invalid entity: empty entity-name") + assert.EqualError(t, err, "error at index 0: invalid entity: empty entity-name") } func TestEntities_Valid_ok(t *testing.T) { - e := Entity{} + e := &Entity{} require.NotNil(t, e.SetEntityName("ACME Ltd."). @@ -67,7 +67,7 @@ func TestEntities_Valid_ok(t *testing.T) { SetRoles(RoleTagCreator, RoleCreator), ) - tv := NewEntities().AddEntity(e) + tv := NewEntities().Add(e) require.NotNil(t, tv) err := tv.Valid() @@ -119,7 +119,7 @@ type testEntityNameBadType struct { testEntityName } -func newTestEntityNameBadType(val any) (*EntityName, error) { +func newTestEntityNameBadType(_ any) (*EntityName, error) { v := testEntityNameBadType{testEntityName(7)} return &EntityName{&v}, nil } diff --git a/comid/example_cca_realm_refval_test.go b/comid/example_cca_realm_refval_test.go index 9de4e43b..b2965eb7 100644 --- a/comid/example_cca_realm_refval_test.go +++ b/comid/example_cca_realm_refval_test.go @@ -50,7 +50,7 @@ func extractRealmRefVals(c *Comid) error { return fmt.Errorf("no reference values triples") } - for i, rv := range *c.Triples.ReferenceValues { + for i, rv := range c.Triples.ReferenceValues.Values { if err := extractRealmRefVal(rv); err != nil { return fmt.Errorf("bad Realm reference value at index %d: %w", i, err) } @@ -59,7 +59,7 @@ func extractRealmRefVals(c *Comid) error { return nil } -func extractRealmRefVal(rv ReferenceValue) error { +func extractRealmRefVal(rv ValueTriple) error { class := rv.Environment.Class instance := rv.Environment.Instance @@ -81,11 +81,11 @@ func extractRealmRefVal(rv ReferenceValue) error { } func extractMeasurements(m Measurements) error { - if len(m) == 0 { + if len(m.Values) == 0 { return fmt.Errorf("no measurements") } - for i, meas := range m { + for i, meas := range m.Values { if err := extractMeasurement(meas); err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } diff --git a/comid/example_cca_refval_test.go b/comid/example_cca_refval_test.go index 51bb5a21..92d0d49d 100644 --- a/comid/example_cca_refval_test.go +++ b/comid/example_cca_refval_test.go @@ -43,7 +43,7 @@ func extractCcaRefVals(c *Comid) error { return fmt.Errorf("no reference values triples") } - for i, rv := range *c.Triples.ReferenceValues { + for i, rv := range c.Triples.ReferenceValues.Values { if err := extractCCARefVal(rv); err != nil { return fmt.Errorf("bad PSA reference value at index %d: %w", i, err) } @@ -52,14 +52,14 @@ func extractCcaRefVals(c *Comid) error { return nil } -func extractCCARefVal(rv ReferenceValue) error { +func extractCCARefVal(rv ValueTriple) error { class := rv.Environment.Class if err := extractImplementationID(class); err != nil { return fmt.Errorf("extracting impl-id: %w", err) } - for i, m := range rv.Measurements { + for i, m := range rv.Measurements.Values { if m.Key == nil { return fmt.Errorf("missing mKey at index %d", i) } diff --git a/comid/example_psa_refval_test.go b/comid/example_psa_refval_test.go index c4b12cd6..7fa78313 100644 --- a/comid/example_psa_refval_test.go +++ b/comid/example_psa_refval_test.go @@ -41,7 +41,7 @@ func extractRefVals(c *Comid) error { return fmt.Errorf("no reference values triples") } - for i, rv := range *c.Triples.ReferenceValues { + for i, rv := range c.Triples.ReferenceValues.Values { if err := extractPSARefVal(rv); err != nil { return fmt.Errorf("bad PSA reference value at index %d: %w", i, err) } @@ -50,7 +50,7 @@ func extractRefVals(c *Comid) error { return nil } -func extractPSARefVal(rv ReferenceValue) error { +func extractPSARefVal(rv ValueTriple) error { class := rv.Environment.Class if err := extractImplementationID(class); err != nil { @@ -67,11 +67,11 @@ func extractPSARefVal(rv ReferenceValue) error { } func extractSwMeasurements(m Measurements) error { - if len(m) == 0 { + if len(m.Values) == 0 { return fmt.Errorf("no measurements") } - for i, m := range m { + for i, m := range m.Values { if err := extractSwMeasurement(m); err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } diff --git a/comid/example_test.go b/comid/example_test.go index 9d3985c6..1c8466e2 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -19,7 +19,7 @@ func Example_encode() { AddLinkedTag("my-ns:acme-roadrunner-base", RelSupplements). AddLinkedTag("my-ns:acme-roadrunner-old", RelReplaces). AddReferenceValue( - ReferenceValue{ + ValueTriple{ Environment: Environment{ Class: NewClassOID(TestOID). SetVendor("ACME Ltd."). @@ -30,7 +30,7 @@ func Example_encode() { Group: MustNewUUIDGroup(TestUUID), }, Measurements: *NewMeasurements(). - AddMeasurement( + Add( MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetSVN(2). @@ -47,7 +47,7 @@ func Example_encode() { }, ). AddEndorsedValue( - EndorsedValue{ + ValueTriple{ Environment: Environment{ Class: NewClassUUID(TestUUID). SetVendor("ACME Ltd."). @@ -58,7 +58,7 @@ func Example_encode() { Group: MustNewUUIDGroup(TestUUID), }, Measurements: *NewMeasurements(). - AddMeasurement( + Add( MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetMinSVN(2). @@ -116,20 +116,20 @@ func Example_encode_PSA() { SetTagIdentity("my-ns:acme-roadrunner-supplement", 0). AddEntity("ACME Ltd.", &TestRegID, RoleCreator, RoleTagCreator, RoleMaintainer). AddReferenceValue( - ReferenceValue{ + ValueTriple{ Environment: Environment{ Class: NewClassImplID(TestImplID). SetVendor("ACME Ltd."). SetModel("RoadRunner 2.0"), }, Measurements: *NewMeasurements(). - AddMeasurement( + Add( MustNewPSAMeasurement( MustCreatePSARefValID( TestSignerID, "BL", "5.0.5", )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), ). - AddMeasurement( + Add( MustNewPSAMeasurement( MustCreatePSARefValID( TestSignerID, "PRoT", "1.3.5", diff --git a/comid/extensions.go b/comid/extensions.go index fa90f43c..522ad6b3 100644 --- a/comid/extensions.go +++ b/comid/extensions.go @@ -6,6 +6,18 @@ import ( "github.com/veraison/corim/extensions" ) +const ( + ExtComid extensions.Point = "Comid" + ExtEntity extensions.Point = "ComidEntity" + ExtTriples extensions.Point = "Triples" + ExtReferenceValue extensions.Point = "ReferenceValue" + ExtReferenceValueFlags extensions.Point = "ReferenceValueFlags" + ExtEndorsedValue extensions.Point = "EndorsedValue" + ExtEndorsedValueFlags extensions.Point = "EndorsedValueFlags" + ExtMval extensions.Point = "Mval" + ExtFlags extensions.Point = "Flags" +) + type IComidConstrainer interface { ConstrainComid(*Comid) error } @@ -43,7 +55,7 @@ func (o *Extensions) validComid(comid *Comid) error { return nil } - ev, ok := o.IExtensionsValue.(IComidConstrainer) + ev, ok := o.IMapValue.(IComidConstrainer) if ok { if err := ev.ConstrainComid(comid); err != nil { return err @@ -58,7 +70,7 @@ func (o *Extensions) validTriples(triples *Triples) error { return nil } - ev, ok := o.IExtensionsValue.(ITriplesConstrainer) + ev, ok := o.IMapValue.(ITriplesConstrainer) if ok { if err := ev.ValidTriples(triples); err != nil { return err @@ -73,7 +85,7 @@ func (o *Extensions) validMval(triples *Mval) error { return nil } - ev, ok := o.IExtensionsValue.(IMvalConstrainer) + ev, ok := o.IMapValue.(IMvalConstrainer) if ok { if err := ev.ConstrainMval(triples); err != nil { return err @@ -88,7 +100,7 @@ func (o *Extensions) validEntity(triples *Entity) error { return nil } - ev, ok := o.IExtensionsValue.(IEntityConstrainer) + ev, ok := o.IMapValue.(IEntityConstrainer) if ok { if err := ev.ConstrainEntity(triples); err != nil { return err @@ -103,7 +115,7 @@ func (o *Extensions) validFlagsMap(triples *FlagsMap) error { return nil } - ev, ok := o.IExtensionsValue.(IFlagsMapConstrainer) + ev, ok := o.IMapValue.(IFlagsMapConstrainer) if ok { if err := ev.ConstrainFlagsMap(triples); err != nil { return err @@ -118,7 +130,7 @@ func (o *Extensions) setTrue(flag Flag) { return } - ev, ok := o.IExtensionsValue.(IFlagSetter) + ev, ok := o.IMapValue.(IFlagSetter) if ok { ev.SetTrue(flag) } @@ -129,7 +141,7 @@ func (o *Extensions) setFalse(flag Flag) { return } - ev, ok := o.IExtensionsValue.(IFlagSetter) + ev, ok := o.IMapValue.(IFlagSetter) if ok { ev.SetFalse(flag) } @@ -140,7 +152,7 @@ func (o *Extensions) clear(flag Flag) { return } - ev, ok := o.IExtensionsValue.(IFlagSetter) + ev, ok := o.IMapValue.(IFlagSetter) if ok { ev.Clear(flag) } @@ -151,7 +163,7 @@ func (o *Extensions) get(flag Flag) *bool { return nil } - ev, ok := o.IExtensionsValue.(IFlagSetter) + ev, ok := o.IMapValue.(IFlagSetter) if ok { return ev.Get(flag) } @@ -164,7 +176,7 @@ func (o *Extensions) anySet() bool { return false } - ev, ok := o.IExtensionsValue.(IFlagSetter) + ev, ok := o.IMapValue.(IFlagSetter) if ok { return ev.AnySet() } diff --git a/comid/extensions_test.go b/comid/extensions_test.go index 01897574..e68fd92c 100644 --- a/comid/extensions_test.go +++ b/comid/extensions_test.go @@ -15,23 +15,23 @@ type TestExtension struct { TestFlag *bool } -func (o *TestExtension) ConstrainComid(v *Comid) error { +func (o *TestExtension) ConstrainComid(_ *Comid) error { return errors.New("invalid") } -func (o *TestExtension) ValidTriples(v *Triples) error { +func (o *TestExtension) ValidTriples(_ *Triples) error { return errors.New("invalid") } -func (o *TestExtension) ConstrainMval(v *Mval) error { +func (o *TestExtension) ConstrainMval(_ *Mval) error { return errors.New("invalid") } -func (o *TestExtension) ConstrainFlagsMap(v *FlagsMap) error { +func (o *TestExtension) ConstrainFlagsMap(_ *FlagsMap) error { return errors.New("invalid") } -func (o *TestExtension) ConstrainEntity(v *Entity) error { +func (o *TestExtension) ConstrainEntity(_ *Entity) error { return errors.New("invalid") } diff --git a/comid/flagsmap.go b/comid/flagsmap.go index f7aa1001..de19147f 100644 --- a/comid/flagsmap.go +++ b/comid/flagsmap.go @@ -4,6 +4,8 @@ package comid import ( + "fmt" + "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" ) @@ -65,6 +67,16 @@ func NewFlagsMap() *FlagsMap { return &FlagsMap{} } +func (o FlagsMap) IsEmpty() bool { + if o.IsConfigured != nil || o.IsSecure != nil || o.IsRecovery != nil || + o.IsDebug != nil || o.IsReplayProtected != nil || o.IsIntegrityProtected != nil || + o.IsRuntimeMeasured != nil || o.IsImmutable != nil || o.IsTcb != nil { + return false + } + + return o.Extensions.IsEmpty() +} + func (o *FlagsMap) AnySet() bool { if o.IsConfigured != nil || o.IsSecure != nil || o.IsRecovery != nil || o.IsDebug != nil || o.IsReplayProtected != nil || o.IsIntegrityProtected != nil || @@ -182,13 +194,22 @@ func (o *FlagsMap) Get(flag Flag) *bool { } // RegisterExtensions registers a struct as a collections of extensions -func (o *FlagsMap) RegisterExtensions(exts extensions.IExtensionsValue) { - o.Extensions.Register(exts) +func (o *FlagsMap) RegisterExtensions(exts extensions.Map) error { + for p, v := range exts { + switch p { + case ExtFlags: + o.Extensions.Register(v) + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + + return nil } -// GetExtensions returns pervisouosly registered extension -func (o *FlagsMap) GetExtensions() extensions.IExtensionsValue { - return o.Extensions.IExtensionsValue +// GetExtensions returns previously registered extension +func (o *FlagsMap) GetExtensions() extensions.IMapValue { + return o.Extensions.IMapValue } // UnmarshalCBOR deserializes from CBOR diff --git a/comid/group_test.go b/comid/group_test.go index cdc571fc..6f3c79f2 100644 --- a/comid/group_test.go +++ b/comid/group_test.go @@ -13,7 +13,7 @@ import ( type testGroup uint64 -func newTestGroup(val any) (*Group, error) { +func newTestGroup(_ any) (*Group, error) { v := testGroup(7) return &Group{&v}, nil } @@ -39,7 +39,7 @@ type testGroupBadType struct { testGroup } -func newTestGroupBadType(val any) (*Group, error) { +func newTestGroupBadType(_ any) (*Group, error) { v := testGroupBadType{testGroup(7)} return &Group{&v}, nil } diff --git a/comid/instance_test.go b/comid/instance_test.go index 744c0f8c..64ca8856 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -46,7 +46,7 @@ func TestInstance_SetGetUEID_OK(t *testing.T) { type testInstance string -func newTestInstance(val any) (*Instance, error) { +func newTestInstance(_ any) (*Instance, error) { ret := testInstance("test") return &Instance{&ret}, nil } diff --git a/comid/measurement.go b/comid/measurement.go index f6ee8c27..ac626246 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -32,7 +32,7 @@ func NewMkey(val any, typ string) (*Mkey, error) { return nil, fmt.Errorf("unexpected measurement key type: %q", typ) } - return factory(nil) + return factory(val) } // MustNewMkey is like NewMkey, execept it does not return an error, assuming @@ -329,7 +329,7 @@ func RegisterMkeyType(tag uint64, factory IMkeyFactory) error { typ := nilVal.Value.Type() if _, exists := mkeyValueRegister[typ]; exists { - return fmt.Errorf("mesurement key type with name %q already exists", typ) + return fmt.Errorf("measurement key type with name %q already exists", typ) } if err := registerCOMIDTag(tag, nilVal.Value); err != nil { @@ -359,13 +359,28 @@ type Mval struct { } // RegisterExtensions registers a struct as a collections of extensions -func (o *Mval) RegisterExtensions(exts extensions.IExtensionsValue) { - o.Extensions.Register(exts) +func (o *Mval) RegisterExtensions(exts extensions.Map) error { + for p, v := range exts { + switch p { + case ExtMval: + o.Extensions.Register(v) + case ExtFlags: + if o.Flags == nil { + o.Flags = new(FlagsMap) + } + + o.Flags.Extensions.Register(v) + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + + return nil } // GetExtensions returns pervisouosly registered extension -func (o *Mval) GetExtensions() extensions.IExtensionsValue { - return o.Extensions.IExtensionsValue +func (o *Mval) GetExtensions() extensions.IMapValue { + return o.Extensions.IMapValue } // UnmarshalCBOR deserializes from CBOR @@ -375,6 +390,16 @@ func (o *Mval) UnmarshalCBOR(data []byte) error { // MarshalCBOR serializes to CBOR func (o Mval) MarshalCBOR() ([]byte, error) { + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.Flags != nil && o.Flags.IsEmpty() { + o.Flags = nil + } + return encoding.SerializeStructToCBOR(em, o) } @@ -385,6 +410,16 @@ func (o *Mval) UnmarshalJSON(data []byte) error { // MarshalJSON serializes to JSON func (o Mval) MarshalJSON() ([]byte, error) { + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.Flags != nil && o.Flags.IsEmpty() { + o.Flags = nil + } + return encoding.SerializeStructToJSON(o) } @@ -570,6 +605,14 @@ func NewOIDMeasurement(key any) (*Measurement, error) { return NewMeasurement(key, OIDType) } +func (o *Measurement) RegisterExtensions(exts extensions.Map) error { + return o.Val.RegisterExtensions(exts) +} + +func (o Measurement) GetExtensions() extensions.IMapValue { + return o.Val.GetExtensions() +} + func (o *Measurement) SetVersion(ver string, scheme int64) *Measurement { if o != nil { v := NewVersion().SetVersion(ver).SetScheme(scheme) @@ -728,10 +771,50 @@ func (o Measurement) Valid() error { } } - // check non-empty<> condition on measurement-values-map - if err := o.Val.Valid(); err != nil { - return err - } + return o.Val.Valid() +} - return nil +// Measurements is a container for Measurement instances and their extensions. +// It is a thin wrapper around extensions.Collection. +type Measurements extensions.Collection[Measurement, *Measurement] + +func NewMeasurements() *Measurements { + return (*Measurements)(extensions.NewCollection[Measurement]()) +} + +func (o *Measurements) RegisterExtensions(exts extensions.Map) error { + return (*extensions.Collection[Measurement, *Measurement])(o).RegisterExtensions(exts) +} + +func (o *Measurements) GetExtensions() extensions.IMapValue { + return (*extensions.Collection[Measurement, *Measurement])(o).GetExtensions() +} + +func (o *Measurements) Valid() error { + return (*extensions.Collection[Measurement, *Measurement])(o).Valid() +} + +func (o *Measurements) IsEmpty() bool { + return (*extensions.Collection[Measurement, *Measurement])(o).IsEmpty() +} + +func (o *Measurements) Add(val *Measurement) *Measurements { + ret := (*extensions.Collection[Measurement, *Measurement])(o).Add(val) + return (*Measurements)(ret) +} + +func (o Measurements) MarshalCBOR() ([]byte, error) { + return (extensions.Collection[Measurement, *Measurement])(o).MarshalCBOR() +} + +func (o *Measurements) UnmarshalCBOR(data []byte) error { + return (*extensions.Collection[Measurement, *Measurement])(o).UnmarshalCBOR(data) +} + +func (o Measurements) MarshalJSON() ([]byte, error) { + return (extensions.Collection[Measurement, *Measurement])(o).MarshalJSON() +} + +func (o *Measurements) UnmarshalJSON(data []byte) error { + return (*extensions.Collection[Measurement, *Measurement])(o).UnmarshalJSON(data) } diff --git a/comid/measurement_test.go b/comid/measurement_test.go index 77a1335e..078c7ee9 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -524,7 +524,7 @@ func TestNewMkeyOID(t *testing.T) { type testMkey [4]byte -func newTestMkey(val any) (*Mkey, error) { +func newTestMkey(_ any) (*Mkey, error) { return &Mkey{&testMkey{0x74, 0x64, 0x73, 0x74}}, nil } @@ -552,7 +552,7 @@ func (o badMkey) Type() string { return "uuid" } -func newBadMkey(val any) (*Mkey, error) { +func newBadMkey(_ any) (*Mkey, error) { return &Mkey{&badMkey{testMkey{0x74, 0x64, 0x73, 0x74}}}, nil } @@ -561,7 +561,7 @@ func TestRegisterMkeyType(t *testing.T) { assert.EqualError(t, err, "tag 32 is already registered") err = RegisterMkeyType(99996, newBadMkey) - assert.EqualError(t, err, `mesurement key type with name "uuid" already exists`) + assert.EqualError(t, err, `measurement key type with name "uuid" already exists`) err = RegisterMkeyType(99996, newTestMkey) assert.NoError(t, err) @@ -581,3 +581,23 @@ func TestMkey_UnmarshalJSON_regression_issue_100(t *testing.T) { assert.Nil(t, err) assert.Equal(t, expected, actual) } + +func TestMkey_new(t *testing.T) { + psaRefValID, err := NewPSARefValID(TestSignerID) + require.NoError(t, err) + + key := MustNewMkey(psaRefValID, PSARefValIDType) + assert.EqualValues(t, psaRefValID, key.Value) + assert.Equal(t, PSARefValIDType, key.Type()) +} + +func TestMkey_UintMkey(t *testing.T) { + var v uint64 = 7 + key, err := NewMkey(v, UintType) + assert.NoError(t, err) + assert.Equal(t, "7", key.Value.String()) + + ret, err := key.GetKeyUint() + assert.NoError(t, err) + assert.EqualValues(t, 7, ret) +} diff --git a/comid/measurements.go b/comid/measurements.go deleted file mode 100644 index 6dd311e8..00000000 --- a/comid/measurements.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import ( - "errors" - "fmt" -) - -// Measurements is an array of Measurement -type Measurements []Measurement - -// NewMeasurements instantiates an empty Measurements array -func NewMeasurements() *Measurements { - return new(Measurements) -} - -// AddMeasurements adds the supplied Measurement to the target Measurement -func (o *Measurements) AddMeasurement(m *Measurement) *Measurements { - if o != nil && m != nil { - *o = append(*o, *m) - } - return o -} - -func (o Measurements) Valid() error { - if len(o) == 0 { - return errors.New("no measurement entries") - } - - for i, m := range o { - if err := m.Valid(); err != nil { - return fmt.Errorf("measurement at index %d: %w", i, err) - } - } - return nil -} diff --git a/comid/oid.go b/comid/oid.go index a080b1fd..fa36dd26 100644 --- a/comid/oid.go +++ b/comid/oid.go @@ -144,11 +144,7 @@ func (o *OID) UnmarshalJSON(data []byte) error { return err } - if err := o.FromString(s); err != nil { - return err - } - - return nil + return o.FromString(s) } func (o OID) MarshalJSON() ([]byte, error) { @@ -219,11 +215,7 @@ func (o *TaggedOID) UnmarshalJSON(data []byte) error { return err } - if err := o.FromString(s); err != nil { - return err - } - - return nil + return o.FromString(s) } func (o TaggedOID) MarshalJSON() ([]byte, error) { diff --git a/comid/referencevalue.go b/comid/referencevalue.go deleted file mode 100644 index a848d682..00000000 --- a/comid/referencevalue.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import "fmt" - -type ReferenceValue struct { - _ struct{} `cbor:",toarray"` - Environment Environment `json:"environment"` - Measurements Measurements `json:"measurements"` -} - -func (o ReferenceValue) Valid() error { - if err := o.Environment.Valid(); err != nil { - return fmt.Errorf("environment validation failed: %w", err) - } - - if err := o.Measurements.Valid(); err != nil { - return fmt.Errorf("measurements validation failed: %w", err) - } - - return nil -} diff --git a/comid/svn_test.go b/comid/svn_test.go index 7a4841fb..6127e0bf 100644 --- a/comid/svn_test.go +++ b/comid/svn_test.go @@ -141,7 +141,7 @@ func TestSVN_JSON(t *testing.T) { type testSVN uint64 -func newTestSVN(val any) (*SVN, error) { +func newTestSVN(_ any) (*SVN, error) { v := testSVN(7) return &SVN{&v}, nil } @@ -162,7 +162,7 @@ type testSVNBadType struct { testSVN } -func newTestSVNBadType(val any) (*SVN, error) { +func newTestSVNBadType(_ any) (*SVN, error) { v := testSVNBadType{testSVN(7)} return &SVN{&v}, nil } diff --git a/comid/triples.go b/comid/triples.go index 7aec9dff..d0327422 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -11,8 +11,8 @@ import ( ) type Triples struct { - ReferenceValues *[]ReferenceValue `cbor:"0,keyasint,omitempty" json:"reference-values,omitempty"` - EndorsedValues *[]EndorsedValue `cbor:"1,keyasint,omitempty" json:"endorsed-values,omitempty"` + ReferenceValues *ValueTriples `cbor:"0,keyasint,omitempty" json:"reference-values,omitempty"` + EndorsedValues *ValueTriples `cbor:"1,keyasint,omitempty" json:"endorsed-values,omitempty"` AttestVerifKeys *[]AttestVerifKey `cbor:"2,keyasint,omitempty" json:"attester-verification-keys,omitempty"` DevIdentityKeys *[]DevIdentityKey `cbor:"3,keyasint,omitempty" json:"dev-identity-keys,omitempty"` @@ -20,13 +20,53 @@ type Triples struct { } // RegisterExtensions registers a struct as a collections of extensions -func (o *Triples) RegisterExtensions(exts extensions.IExtensionsValue) { - o.Extensions.Register(exts) +func (o *Triples) RegisterExtensions(exts extensions.Map) error { + refValExts := extensions.NewMap() + endValExts := extensions.NewMap() + + for p, v := range exts { + switch p { + case ExtTriples: + o.Extensions.Register(v) + case ExtReferenceValue: + refValExts[ExtMval] = v + case ExtReferenceValueFlags: + refValExts[ExtFlags] = v + case ExtEndorsedValue: + endValExts[ExtMval] = v + case ExtEndorsedValueFlags: + endValExts[ExtFlags] = v + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + + if len(refValExts) != 0 { + if o.ReferenceValues == nil { + o.ReferenceValues = NewValueTriples() + } + + if err := o.ReferenceValues.RegisterExtensions(refValExts); err != nil { + return err + } + } + + if len(endValExts) != 0 { + if o.EndorsedValues == nil { + o.EndorsedValues = NewValueTriples() + } + + if err := o.EndorsedValues.RegisterExtensions(refValExts); err != nil { + return err + } + } + + return nil } // GetExtensions returns pervisouosly registered extension -func (o *Triples) GetExtensions() extensions.IExtensionsValue { - return o.Extensions.IExtensionsValue +func (o *Triples) GetExtensions() extensions.IMapValue { + return o.Extensions.IMapValue } // UnmarshalCBOR deserializes from CBOR @@ -36,6 +76,20 @@ func (o *Triples) UnmarshalCBOR(data []byte) error { // MarshalCBOR serializes to CBOR func (o Triples) MarshalCBOR() ([]byte, error) { + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.ReferenceValues != nil && o.ReferenceValues.IsEmpty() { + o.ReferenceValues = nil + } + + if o.EndorsedValues != nil && o.EndorsedValues.IsEmpty() { + o.EndorsedValues = nil + } + return encoding.SerializeStructToCBOR(em, o) } @@ -46,30 +100,42 @@ func (o *Triples) UnmarshalJSON(data []byte) error { // MarshalJSON serializes to JSON func (o Triples) MarshalJSON() ([]byte, error) { + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.ReferenceValues != nil && o.ReferenceValues.IsEmpty() { + o.ReferenceValues = nil + } + + if o.EndorsedValues != nil && o.EndorsedValues.IsEmpty() { + o.EndorsedValues = nil + } + return encoding.SerializeStructToJSON(o) } // Valid checks that the Triples is valid as per the specification func (o Triples) Valid() error { // non-empty<> - if o.ReferenceValues == nil && o.EndorsedValues == nil && - o.AttestVerifKeys == nil && o.DevIdentityKeys == nil { + if (o.ReferenceValues == nil || o.ReferenceValues.IsEmpty()) && + (o.EndorsedValues == nil || o.EndorsedValues.IsEmpty()) && + (o.AttestVerifKeys == nil || len(*o.AttestVerifKeys) == 0) && + (o.DevIdentityKeys == nil || len(*o.DevIdentityKeys) == 0) { return fmt.Errorf("triples struct must not be empty") } if o.ReferenceValues != nil { - for i, rv := range *o.ReferenceValues { - if err := rv.Valid(); err != nil { - return fmt.Errorf("reference value at index %d: %w", i, err) - } + if err := o.ReferenceValues.Valid(); err != nil { + return fmt.Errorf("reference values: %w", err) } } if o.EndorsedValues != nil { - for i, ev := range *o.EndorsedValues { - if err := ev.Valid(); err != nil { - return fmt.Errorf("endorsed value at index %d: %w", i, err) - } + if err := o.EndorsedValues.Valid(); err != nil { + return fmt.Errorf("endorsed values: %w", err) } } @@ -92,17 +158,27 @@ func (o Triples) Valid() error { return o.Extensions.validTriples(&o) } -func (o *Triples) AddReferenceValue(val ReferenceValue) *Triples { +func (o *Triples) AddReferenceValue(val ValueTriple) *Triples { if o != nil { - *o.ReferenceValues = append(*o.ReferenceValues, val) + if o.ReferenceValues == nil { + o.ReferenceValues = new(ValueTriples) + } + + o.ReferenceValues.Add(&val) } + return o } -func (o *Triples) AddEndorsedValue(val EndorsedValue) *Triples { +func (o *Triples) AddEndorsedValue(val ValueTriple) *Triples { if o != nil { - *o.EndorsedValues = append(*o.EndorsedValues, val) + if o.EndorsedValues == nil { + o.EndorsedValues = new(ValueTriples) + } + + o.EndorsedValues.Add(&val) } + return o } @@ -110,6 +186,7 @@ func (o *Triples) AddAttestVerifKey(val AttestVerifKey) *Triples { if o != nil { *o.AttestVerifKeys = append(*o.AttestVerifKeys, val) } + return o } @@ -117,5 +194,6 @@ func (o *Triples) AddDevIdentityKey(val DevIdentityKey) *Triples { if o != nil { *o.DevIdentityKeys = append(*o.DevIdentityKeys, val) } + return o } diff --git a/comid/triples_test.go b/comid/triples_test.go new file mode 100644 index 00000000..e5183827 --- /dev/null +++ b/comid/triples_test.go @@ -0,0 +1,86 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/extensions" +) + +func TestTriples_extensions(t *testing.T) { + triples := Triples{} + triplesExt := &struct{}{} + + extMap := extensions.NewMap(). + Add(ExtTriples, triplesExt). + Add(ExtReferenceValue, &struct{}{}). + Add(ExtReferenceValueFlags, &struct{}{}). + Add(ExtEndorsedValue, &struct{}{}). + Add(ExtEndorsedValueFlags, &struct{}{}) + + err := triples.RegisterExtensions(extMap) + assert.NoError(t, err) + assert.Equal(t, triplesExt, triples.GetExtensions()) + + badMap := extensions.NewMap().Add(extensions.Point("test"), &struct{}{}) + err = triples.RegisterExtensions(badMap) + assert.EqualError(t, err, `unexpected extension point: "test"`) +} + +func TestTriples_marshaling(t *testing.T) { + triples := Triples{} + + extMap := extensions.NewMap(). + Add(ExtReferenceValue, &struct{}{}). + Add(ExtEndorsedValue, &struct{}{}) + + require.NoError(t, triples.RegisterExtensions(extMap)) + + data, err := triples.MarshalCBOR() + assert.NoError(t, err) + assert.Equal(t, data, []byte{0xa0}) + + data, err = triples.MarshalJSON() + assert.NoError(t, err) + assert.JSONEq(t, "{}", string(data)) +} + +func TestTriples_Valid(t *testing.T) { + triples := Triples{} + triples.ReferenceValues = &ValueTriples{} + triples.EndorsedValues = &ValueTriples{} + + err := triples.Valid() + assert.EqualError(t, err, "triples struct must not be empty") + + triples.ReferenceValues.Add(&ValueTriple{}) + err = triples.Valid() + assert.EqualError(t, err, "reference values: error at index 0: environment validation failed: environment must not be empty") + + triples.ReferenceValues = nil + triples.EndorsedValues.Add(&ValueTriple{}) + err = triples.Valid() + assert.EqualError(t, err, "endorsed values: error at index 0: environment validation failed: environment must not be empty") + + triples.EndorsedValues = nil + triples.AttestVerifKeys = &[]AttestVerifKey{{}} + err = triples.Valid() + assert.EqualError(t, err, "attestation verification key at index 0: environment validation failed: environment must not be empty") + + triples.AttestVerifKeys = nil + triples.DevIdentityKeys = &[]DevIdentityKey{{}} + err = triples.Valid() + assert.EqualError(t, err, "device identity key at index 0: environment validation failed: environment must not be empty") +} + +func TestTriples_adders(t *testing.T) { + triples := Triples{} + + triples.AddReferenceValue(ValueTriple{}).AddEndorsedValue(ValueTriple{}) + assert.Len(t, triples.ReferenceValues.Values, 1) + assert.Len(t, triples.EndorsedValues.Values, 1) +} diff --git a/comid/valuetriple.go b/comid/valuetriple.go new file mode 100644 index 00000000..a7662ea0 --- /dev/null +++ b/comid/valuetriple.go @@ -0,0 +1,91 @@ +// Copyright 2021-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "errors" + "fmt" + + "github.com/veraison/corim/extensions" +) + +// ValueTriple relates measurements to a target environment, essentially +// forming a subject-predicate-object triple of "measurements-pertain +// to-environment". This structure is used to represent both +// reference-triple-record and endorsed-triple-record in the CoRIM spec (as of +// rev. 04). +type ValueTriple struct { + _ struct{} `cbor:",toarray"` + Environment Environment `json:"environment"` + Measurements Measurements `json:"measurements"` +} + +func (o *ValueTriple) RegisterExtensions(exts extensions.Map) error { + return o.Measurements.RegisterExtensions(exts) +} + +func (o *ValueTriple) GetExtensions() extensions.IMapValue { + return o.Measurements.GetExtensions() +} + +func (o ValueTriple) Valid() error { + if err := o.Environment.Valid(); err != nil { + return fmt.Errorf("environment validation failed: %w", err) + } + + if o.Measurements.IsEmpty() { + return errors.New("measurements validation failed: no measurement entries") + } + + if err := o.Measurements.Valid(); err != nil { + return fmt.Errorf("measurements validation failed: %w", err) + } + + return nil +} + +// ValueTriples is a container for ValueTriple instances and their extensions. +// It is a thin wrapper around extensions.Collection. +type ValueTriples extensions.Collection[ValueTriple, *ValueTriple] + +func NewValueTriples() *ValueTriples { + return (*ValueTriples)(extensions.NewCollection[ValueTriple]()) +} + +func (o *ValueTriples) RegisterExtensions(exts extensions.Map) error { + return (*extensions.Collection[ValueTriple, *ValueTriple])(o).RegisterExtensions(exts) +} + +func (o *ValueTriples) GetExtensions() extensions.IMapValue { + return (*extensions.Collection[ValueTriple, *ValueTriple])(o).GetExtensions() +} + +func (o ValueTriples) Valid() error { + return (extensions.Collection[ValueTriple, *ValueTriple])(o).Valid() +} + +func (o *ValueTriples) IsEmpty() bool { + return (*extensions.Collection[ValueTriple, *ValueTriple])(o).IsEmpty() +} + +func (o *ValueTriples) Add(val *ValueTriple) *ValueTriples { + ret := (*extensions.Collection[ValueTriple, *ValueTriple])(o).Add(val) + return (*ValueTriples)(ret) +} + +func (o ValueTriples) MarshalCBOR() ([]byte, error) { + return (extensions.Collection[ValueTriple, *ValueTriple])(o).MarshalCBOR() +} + +func (o *ValueTriples) UnmarshalCBOR(data []byte) error { + return (*extensions.Collection[ValueTriple, *ValueTriple])(o).UnmarshalCBOR(data) +} + +func (o ValueTriples) MarshalJSON() ([]byte, error) { + return (extensions.Collection[ValueTriple, *ValueTriple])(o).MarshalJSON() +} + +func (o *ValueTriples) UnmarshalJSON(data []byte) error { + return (*extensions.Collection[ValueTriple, *ValueTriple])(o).UnmarshalJSON(data) +} diff --git a/comid/referencevalue_test.go b/comid/valuetriple_test.go similarity index 96% rename from comid/referencevalue_test.go rename to comid/valuetriple_test.go index 6fe431f3..0888e32c 100644 --- a/comid/referencevalue_test.go +++ b/comid/valuetriple_test.go @@ -11,7 +11,7 @@ import ( ) func Test_ReferenceValue(t *testing.T) { - rv := ReferenceValue{} + rv := ValueTriple{} err := rv.Valid() assert.EqualError(t, err, "environment validation failed: environment must not be empty") diff --git a/corim/entity.go b/corim/entity.go index 675dcd81..d6129a19 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -28,13 +28,22 @@ func NewEntity() *Entity { } // RegisterExtensions registers a struct as a collections of extensions -func (o *Entity) RegisterExtensions(exts extensions.IExtensionsValue) { - o.Extensions.Register(exts) +func (o *Entity) RegisterExtensions(exts extensions.Map) error { + for p, v := range exts { + switch p { + case ExtEntity: + o.Extensions.Register(v) + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + + return nil } // GetExtensions returns pervisouosly registered extension -func (o *Entity) GetExtensions() extensions.IExtensionsValue { - return o.Extensions.IExtensionsValue +func (o *Entity) GetExtensions() extensions.IMapValue { + return o.Extensions.IMapValue } // SetEntityName is used to set the EntityName field of Entity using supplied name @@ -117,30 +126,49 @@ func (o Entity) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } -// Entities is an array of entity-map's -type Entities []Entity +// Entities is a container for Entity instances and their extensions. +// It is a thin wrapper around extensions.Collection. +type Entities extensions.Collection[Entity, *Entity] -// NewEntities instantiates an empty entity-map array func NewEntities() *Entities { - return new(Entities) + return (*Entities)(extensions.NewCollection[Entity]()) } -// AddEntity adds the supplied entity-map to the target Entities -func (o *Entities) AddEntity(e Entity) *Entities { - if o != nil { - *o = append(*o, e) - } - return o +func (o *Entities) RegisterExtensions(exts extensions.Map) error { + return (*extensions.Collection[Entity, *Entity])(o).RegisterExtensions(exts) } -// Valid iterates over the range of individual entities to check for validity -func (o Entities) Valid() error { - for i, m := range o { - if err := m.Valid(); err != nil { - return fmt.Errorf("entity at index %d: %w", i, err) - } - } - return nil +func (o *Entities) GetExtensions() extensions.IMapValue { + return (*extensions.Collection[Entity, *Entity])(o).GetExtensions() +} + +func (o *Entities) Valid() error { + return (*extensions.Collection[Entity, *Entity])(o).Valid() +} + +func (o *Entities) IsEmpty() bool { + return (*extensions.Collection[Entity, *Entity])(o).IsEmpty() +} + +func (o *Entities) Add(val *Entity) *Entities { + ret := (*extensions.Collection[Entity, *Entity])(o).Add(val) + return (*Entities)(ret) +} + +func (o Entities) MarshalCBOR() ([]byte, error) { + return (extensions.Collection[Entity, *Entity])(o).MarshalCBOR() +} + +func (o *Entities) UnmarshalCBOR(data []byte) error { + return (*extensions.Collection[Entity, *Entity])(o).UnmarshalCBOR(data) +} + +func (o Entities) MarshalJSON() ([]byte, error) { + return (extensions.Collection[Entity, *Entity])(o).MarshalJSON() +} + +func (o *Entities) UnmarshalJSON(data []byte) error { + return (*extensions.Collection[Entity, *Entity])(o).UnmarshalJSON(data) } // EntityName encapsulates the name of the associated Entity. The CoRIM diff --git a/corim/entity_test.go b/corim/entity_test.go index cd7bcc3e..be23a0d4 100644 --- a/corim/entity_test.go +++ b/corim/entity_test.go @@ -78,7 +78,7 @@ func TestEntities_Valid_ok(t *testing.T) { SetRoles(RoleManifestCreator) require.NotNil(t, e) - es := NewEntities().AddEntity(*e) + es := NewEntities().Add(e) require.NotNil(t, es) err := es.Valid() @@ -88,11 +88,11 @@ func TestEntities_Valid_ok(t *testing.T) { func TestEntities_Valid_empty(t *testing.T) { e := Entity{} - es := NewEntities().AddEntity(e) + es := NewEntities().Add(&e) require.NotNil(t, es) err := es.Valid() - assert.EqualError(t, err, "entity at index 0: invalid entity: empty entity-name") + assert.EqualError(t, err, "error at index 0: invalid entity: empty entity-name") } type testEntityName uint64 @@ -128,7 +128,7 @@ type testEntityNameBadType struct { testEntityName } -func newTestEntityNameBadType(val any) (*EntityName, error) { +func newTestEntityNameBadType(_ any) (*EntityName, error) { v := testEntityNameBadType{testEntityName(7)} return &EntityName{&v}, nil } diff --git a/corim/extensions.go b/corim/extensions.go index f7e21546..7bcd8d8c 100644 --- a/corim/extensions.go +++ b/corim/extensions.go @@ -6,6 +6,12 @@ import ( "github.com/veraison/corim/extensions" ) +const ( + ExtUnsignedCorim extensions.Point = "UnsignedCorim" + ExtEntity extensions.Point = "CorimEntity" + ExtSigner extensions.Point = "Signer" +) + type IEntityConstrainer interface { ConstrainEntity(*Entity) error } @@ -27,7 +33,7 @@ func (o *Extensions) validEntity(entity *Entity) error { return nil } - ev, ok := o.IExtensionsValue.(IEntityConstrainer) + ev, ok := o.IMapValue.(IEntityConstrainer) if ok { if err := ev.ConstrainEntity(entity); err != nil { return err @@ -42,7 +48,7 @@ func (o *Extensions) validCorim(c *UnsignedCorim) error { return nil } - ev, ok := o.IExtensionsValue.(ICorimConstrainer) + ev, ok := o.IMapValue.(ICorimConstrainer) if ok { if err := ev.ConstrainCorim(c); err != nil { return err @@ -57,7 +63,7 @@ func (o *Extensions) validSigner(signer *Signer) error { return nil } - ev, ok := o.IExtensionsValue.(ISignerConstrainer) + ev, ok := o.IMapValue.(ISignerConstrainer) if ok { if err := ev.ConstrainSigner(signer); err != nil { return err diff --git a/corim/extensions_test.go b/corim/extensions_test.go index 617b7bd0..0c76235c 100644 --- a/corim/extensions_test.go +++ b/corim/extensions_test.go @@ -9,6 +9,7 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/veraison/corim/extensions" ) type TestExtensions struct { @@ -24,11 +25,11 @@ func (o TestExtensions) ConstrainEntity(ent *Entity) error { return nil } -func (o TestExtensions) ConstrainCorim(c *UnsignedCorim) error { +func (o TestExtensions) ConstrainCorim(_ *UnsignedCorim) error { return errors.New("invalid") } -func (o TestExtensions) ConstrainSigner(s *Signer) error { +func (o TestExtensions) ConstrainSigner(_ *Signer) error { return errors.New("invalid") } @@ -40,7 +41,10 @@ func TestEntityExtensions_Valid(t *testing.T) { err := ent.Valid() assert.NoError(t, err) - ent.RegisterExtensions(&TestExtensions{}) + extMap := extensions.NewMap().Add(ExtEntity, &TestExtensions{}) + err = ent.RegisterExtensions(extMap) + require.NoError(t, err) + err = ent.Valid() assert.EqualError(t, err, `EntityName must be "Futurama"`) @@ -73,9 +77,11 @@ func TestEntityExtensions_CBOR(t *testing.T) { } ent := NewEntity() - ent.RegisterExtensions(&TestExtensions{}) + extMap := extensions.NewMap().Add(ExtEntity, &TestExtensions{}) + err := ent.RegisterExtensions(extMap) + require.NoError(t, err) - err := cbor.Unmarshal(data, &ent) + err = cbor.Unmarshal(data, &ent) assert.NoError(t, err) assert.Equal(t, ent.EntityName.String(), "acme") diff --git a/corim/meta.go b/corim/meta.go index f83730e2..d72fe0d4 100644 --- a/corim/meta.go +++ b/corim/meta.go @@ -5,99 +5,12 @@ package corim import ( "encoding/json" - "errors" "fmt" "time" - "github.com/veraison/corim/comid" - "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" ) -type Signer struct { - Name string `cbor:"0,keyasint" json:"name"` - URI *comid.TaggedURI `cbor:"1,keyasint,omitempty" json:"uri,omitempty"` - - Extensions -} - -func NewSigner() *Signer { - return &Signer{} -} - -// RegisterExtensions registers a struct as a collections of extensions -func (o *Signer) RegisterExtensions(exts extensions.IExtensionsValue) { - o.Extensions.Register(exts) -} - -// GetExtensions returns previously registered extension -func (o *Signer) GetExtensions() extensions.IExtensionsValue { - return o.Extensions.IExtensionsValue -} - -// SetName sets the target Signer's name to the supplied value -func (o *Signer) SetName(name string) *Signer { - if o != nil { - if name == "" { - return nil - } - o.Name = name - } - return o -} - -// SetURI sets the target Signer's URI to the supplied value -func (o *Signer) SetURI(uri string) *Signer { - if o != nil { - if uri == "" { - return nil - } - - taggedURI, err := comid.String2URI(&uri) - if err != nil { - return nil - } - - o.URI = taggedURI - } - return o -} - -// Valid checks the validity of individual fields within Signer -func (o Signer) Valid() error { - if o.Name == "" { - return errors.New("empty name") - } - - if o.URI != nil { - if err := comid.IsAbsoluteURI(string(*o.URI)); err != nil { - return fmt.Errorf("invalid URI: %w", err) - } - } - - return o.Extensions.validSigner(&o) -} - -// UnmarshalCBOR deserializes from CBOR -func (o *Signer) UnmarshalCBOR(data []byte) error { - return encoding.PopulateStructFromCBOR(dm, data, o) -} - -// MarshalCBOR serializes to CBOR -func (o Signer) MarshalCBOR() ([]byte, error) { - return encoding.SerializeStructToCBOR(em, o) -} - -// UnmarshalJSON deserializes from JSON -func (o *Signer) UnmarshalJSON(data []byte) error { - return encoding.PopulateStructFromJSON(data, o) -} - -// MarshalJSON serializes to JSON -func (o Signer) MarshalJSON() ([]byte, error) { - return encoding.SerializeStructToJSON(o) -} - // Meta stores a corim-meta-map with JSON and CBOR serializations. It carries // information about the CoRIM signer and, optionally, a validity period // associated with the signed assertion. A corim-meta-map is serialized to CBOR @@ -111,6 +24,19 @@ func NewMeta() *Meta { return &Meta{} } +func (o *Meta) RegisterExtensions(exts extensions.Map) error { + for p, v := range exts { + switch p { + case ExtSigner: + o.Signer.Extensions.Register(v) + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + + return nil +} + // SetSigner populates the Signer element in the target Meta with the supplied // name and optional URI func (o *Meta) SetSigner(name string, uri *string) *Meta { @@ -147,12 +73,12 @@ func (o *Meta) SetValidity(notAfter time.Time, notBefore *time.Time) *Meta { // Valid checks for validity of the fields within Meta func (o Meta) Valid() error { if err := o.Signer.Valid(); err != nil { - return fmt.Errorf("invalid meta: %w", err) + return fmt.Errorf("invalid signer: %w", err) } if o.Validity != nil { if err := o.Validity.Valid(); err != nil { - return fmt.Errorf("invalid meta: %w", err) + return fmt.Errorf("invalid validity: %w", err) } } diff --git a/corim/meta_test.go b/corim/meta_test.go index 8d04f017..11a55b0f 100644 --- a/corim/meta_test.go +++ b/corim/meta_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" ) var ( @@ -184,14 +185,34 @@ func TestMeta_FromCBOR_full(t *testing.T) { assert.Equal(t, notAfter.Unix(), actual.Validity.NotAfter.Unix()) } -func Test_Signer_Valid(t *testing.T) { - var signer Signer +func Test_Meata_extensions(t *testing.T) { + meta := NewMeta() - assert.EqualError(t, signer.Valid(), "empty name") + exts := &signerExtensions{} + extMap := extensions.NewMap().Add(ExtSigner, exts) - signer.Name = "test-signer" - uri := comid.TaggedURI("@@@") - signer.URI = &uri + err := meta.RegisterExtensions(extMap) + assert.NoError(t, err) + assert.True(t, meta.Signer.Extensions.HaveExtensions()) + assert.Equal(t, exts, meta.Signer.GetExtensions()) - assert.EqualError(t, signer.Valid(), `invalid URI: "@@@" is not an absolute URI`) + badMap := extensions.NewMap().Add(extensions.Point("test"), exts) + err = meta.RegisterExtensions(badMap) + assert.EqualError(t, err, `unexpected extension point: "test"`) +} + +func Test_meta_Valid(t *testing.T) { + meta := NewMeta() + + assert.EqualError(t, meta.Valid(), "invalid signer: empty name") + + uri := "http://example.com" + meta.SetSigner("test", &uri) + assert.NoError(t, meta.Valid()) + + ts := time.Now() + meta.Validity = &Validity{NotBefore: &ts} + err := meta.Valid() + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid validity: invalid not-before / not-after") } diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 2a790420..32d2acfc 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -10,6 +10,7 @@ import ( "fmt" "strings" + "github.com/veraison/corim/extensions" cose "github.com/veraison/go-cose" ) @@ -27,6 +28,24 @@ type SignedCorim struct { message *cose.Sign1Message } +func (o *SignedCorim) RegisterExtensions(exts extensions.Map) error { + unsignedExts := extensions.NewMap() + + for p, v := range exts { + switch p { + case ExtSigner: + signerExts := extensions.NewMap().Add(ExtSigner, v) + if err := o.Meta.RegisterExtensions(signerExts); err != nil { + return err + } + default: + unsignedExts.Add(p, v) + } + } + + return o.UnsignedCorim.RegisterExtensions(unsignedExts) +} + func (o *SignedCorim) processHdrs() error { var hdr = o.message.Headers diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 9cfc4cf2..e6ba9e8d 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/veraison/corim/extensions" ) var ( @@ -423,3 +424,17 @@ func TestSignedCorim_Sign_fail_no_signer(t *testing.T) { _, err := SignedCorimIn.Sign(nil) assert.EqualError(t, err, "nil signer") } + +func TestSignedCorim_extensions(t *testing.T) { + extMap := extensions.NewMap(). + Add(ExtSigner, &struct{}{}). + Add(ExtUnsignedCorim, &struct{}{}) + + var s SignedCorim + err := s.RegisterExtensions(extMap) + assert.NoError(t, err) + + badMap := extensions.NewMap().Add(extensions.Point("test"), &struct{}{}) + err = s.RegisterExtensions(badMap) + assert.EqualError(t, err, `unexpected extension point: "test"`) +} diff --git a/corim/signer.go b/corim/signer.go index 90993888..62e104fb 100644 --- a/corim/signer.go +++ b/corim/signer.go @@ -8,13 +8,110 @@ import ( "crypto/ed25519" "crypto/elliptic" "crypto/rsa" + "errors" "fmt" "reflect" "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" cose "github.com/veraison/go-cose" ) +type Signer struct { + Name string `cbor:"0,keyasint" json:"name"` + URI *comid.TaggedURI `cbor:"1,keyasint,omitempty" json:"uri,omitempty"` + + Extensions +} + +func NewSigner() *Signer { + return &Signer{} +} + +// RegisterExtensions registers a struct as a collections of extensions +func (o *Signer) RegisterExtensions(exts extensions.Map) error { + for p, v := range exts { + switch p { + case ExtSigner: + o.Extensions.Register(v) + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + + return nil +} + +// GetExtensions returns previously registered extension +func (o *Signer) GetExtensions() extensions.IMapValue { + return o.Extensions.IMapValue +} + +// SetName sets the target Signer's name to the supplied value +func (o *Signer) SetName(name string) *Signer { + if o != nil { + if name == "" { + return nil + } + o.Name = name + } + return o +} + +// SetURI sets the target Signer's URI to the supplied value +func (o *Signer) SetURI(uri string) *Signer { + if o != nil { + if uri == "" { + return nil + } + + taggedURI, err := comid.String2URI(&uri) + if err != nil { + return nil + } + + o.URI = taggedURI + } + return o +} + +// Valid checks the validity of individual fields within Signer +func (o Signer) Valid() error { + if o.Name == "" { + return errors.New("empty name") + } + + if o.URI != nil { + if err := comid.IsAbsoluteURI(string(*o.URI)); err != nil { + return fmt.Errorf("invalid URI: %w", err) + } + } + + return o.Extensions.validSigner(&o) +} + +// UnmarshalCBOR deserializes from CBOR +func (o *Signer) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +// MarshalCBOR serializes to CBOR +func (o Signer) MarshalCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) +} + +// UnmarshalJSON deserializes from JSON +func (o *Signer) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// MarshalJSON serializes to JSON +func (o Signer) MarshalJSON() ([]byte, error) { + return encoding.SerializeStructToJSON(o) +} + const noAlg = cose.Algorithm(-65537) func getAlgAndKeyFromJWK(j []byte) (cose.Algorithm, crypto.Signer, error) { diff --git a/corim/signer_test.go b/corim/signer_test.go new file mode 100644 index 00000000..ec6e0520 --- /dev/null +++ b/corim/signer_test.go @@ -0,0 +1,61 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" +) + +type signerExtensions struct { + Address string `cbor:"-1" json:"address"` +} + +func TestSigner_RegisterExtensions(t *testing.T) { + signer := NewSigner() + assert.False(t, signer.Extensions.HaveExtensions()) + + exts := &signerExtensions{} + extMap := extensions.NewMap().Add(ExtSigner, exts) + + err := signer.RegisterExtensions(extMap) + assert.NoError(t, err) + assert.True(t, signer.Extensions.HaveExtensions()) + assert.Equal(t, exts, signer.GetExtensions()) + + badMap := extensions.NewMap().Add(extensions.Point("test"), exts) + err = signer.RegisterExtensions(badMap) + assert.EqualError(t, err, `unexpected extension point: "test"`) +} + +func TestSigner_Valid(t *testing.T) { + var signer Signer + + assert.EqualError(t, signer.Valid(), "empty name") + + signer.Name = "test-signer" + uri := comid.TaggedURI("@@@") + signer.URI = &uri + + assert.EqualError(t, signer.Valid(), `invalid URI: "@@@" is not an absolute URI`) +} + +func TestSigner_JSON(t *testing.T) { + signer := NewSigner() + signer.Name = "test-signer" + uri := comid.TaggedURI("https://example.com") + signer.URI = &uri + + buf, err := signer.MarshalJSON() + assert.NoError(t, err) + assert.JSONEq(t, `{"name": "test-signer", "uri": "https://example.com"}`, string(buf)) + + other := NewSigner() + err = other.UnmarshalJSON(buf) + assert.NoError(t, err) + assert.Equal(t, signer.Name, other.Name) + assert.Equal(t, signer.URI, other.URI) +} diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 04addbe2..672409d9 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -44,13 +44,31 @@ func NewUnsignedCorim() *UnsignedCorim { } // RegisterExtensions registers a struct as a collections of extensions -func (o *UnsignedCorim) RegisterExtensions(exts extensions.IExtensionsValue) { - o.Extensions.Register(exts) +func (o *UnsignedCorim) RegisterExtensions(exts extensions.Map) error { + for p, v := range exts { + switch p { + case ExtUnsignedCorim: + o.Extensions.Register(v) + case ExtEntity: + if o.Entities == nil { + o.Entities = NewEntities() + } + + entMap := extensions.NewMap().Add(ExtEntity, v) + if err := o.Entities.RegisterExtensions(entMap); err != nil { + return err + } + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + + return nil } // GetExtensions returns pervisouosly registered extension -func (o *UnsignedCorim) GetExtensions() extensions.IExtensionsValue { - return o.Extensions.IExtensionsValue +func (o *UnsignedCorim) GetExtensions() extensions.IMapValue { + return o.Extensions.IMapValue } // SetID sets the corim-id in the unsigned-corim-map to the supplied value. The @@ -207,7 +225,7 @@ func (o *UnsignedCorim) AddEntity(name string, regID *string, roles ...Role) *Un o.Entities = new(Entities) } - if o.Entities.AddEntity(*e) == nil { + if o.Entities.Add(e) == nil { return nil } } @@ -253,7 +271,7 @@ func (o UnsignedCorim) Valid() error { } if o.Entities != nil { - for i, e := range *o.Entities { + for i, e := range o.Entities.Values { if err := e.Valid(); err != nil { return fmt.Errorf("entity validation failed at pos %d: %w", i, err) } @@ -264,7 +282,17 @@ func (o UnsignedCorim) Valid() error { } // ToCBOR serializes the target unsigned CoRIM to CBOR -func (o *UnsignedCorim) ToCBOR() ([]byte, error) { +func (o UnsignedCorim) ToCBOR() ([]byte, error) { + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.Entities != nil && o.Entities.IsEmpty() { + o.Entities = nil + } + return encoding.SerializeStructToCBOR(em, o) } @@ -273,6 +301,21 @@ func (o *UnsignedCorim) FromCBOR(data []byte) error { return encoding.PopulateStructFromCBOR(dm, data, o) } +// ToJSON serializes the target unsigned CoRIM to JSON +func (o UnsignedCorim) ToJSON() ([]byte, error) { + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.Entities != nil && o.Entities.IsEmpty() { + o.Entities = nil + } + + return encoding.SerializeStructToJSON(o) +} + // FromJSON deserializes a JSON-encoded unsigned CoRIM into the target UnsignedCorim func (o *UnsignedCorim) FromJSON(data []byte) error { return encoding.PopulateStructFromJSON(data, o) diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 7374d05a..b9b6b979 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" "github.com/veraison/corim/cots" + "github.com/veraison/corim/extensions" "github.com/veraison/swid" ) @@ -262,10 +263,12 @@ func TestUnsignedCorim_AddEntity_full(t *testing.T) { expected := UnsignedCorim{ Entities: &Entities{ - Entity{ - EntityName: MustNewStringEntityName(name), - Roles: Roles{role}, - RegID: &taggedRegID, + Values: []Entity{ + { + EntityName: MustNewStringEntityName(name), + Roles: Roles{role}, + RegID: &taggedRegID, + }, }, }, } @@ -306,3 +309,114 @@ func TestUnsignedCorim_FromJSON(t *testing.T) { assert.NoError(t, err) } + +func TestUnsignedCorim_ToJSON(t *testing.T) { + c := comid.NewComid(). + SetTagIdentity("vendor.example/prod/1", 0). + AddAttestVerifKey( + comid.AttestVerifKey{ + Environment: comid.Environment{ + Instance: comid.MustNewUUIDInstance(comid.TestUUID), + }, + VerifKeys: *comid.NewCryptoKeys(). + Add( + comid.MustNewPKIXBase64Key(comid.TestECPubKey), + ), + }, + ) + require.NotNil(t, c) + + tv := NewUnsignedCorim(). + SetID("invalid.tags.corim"). + AddDependentRim("http://endorser.example/addon.corim", nil). + AddProfile("https://arm.com/psa/iot/2.0.0"). + AddComid(*c) + + require.NotNil(t, tv) + + extMap := extensions.NewMap().Add(ExtEntity, &struct{}{}) + err := tv.RegisterExtensions(extMap) + assert.NoError(t, err) + + buf, err := tv.ToJSON() + + assert.NoError(t, err) + expectedJSON := ` + { + "corim-id":"invalid.tags.corim", + "tags":["2QH6ogGhAHV2ZW5kb3IuZXhhbXBsZS9wcm9kLzEEoQKBgqEB2CVQMftavwI+SZKqTpX5wVA7+oHZAip4sS0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tCk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVcxQnZxRisvcnk4QldhN1pFTVUxeFlZSEVROEIKbExUNE1GSE9hTytJQ1R0SXZyRWVFcHIvc2ZUQVA2NkgyaENIZGI1SEVYS3RSS29kNlFMY09MUEExUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ=="], + "dependent-rims":[{"href":"http://endorser.example/addon.corim"}], + "profiles":["https://arm.com/psa/iot/2.0.0"] + } + ` + + assert.JSONEq(t, expectedJSON, string(buf)) +} + +func TestUnsignedCorim_ToCBOR(t *testing.T) { + c := comid.NewComid(). + SetTagIdentity("vendor.example/prod/1", 0). + AddAttestVerifKey( + comid.AttestVerifKey{ + Environment: comid.Environment{ + Instance: comid.MustNewUUIDInstance(comid.TestUUID), + }, + VerifKeys: *comid.NewCryptoKeys(). + Add( + comid.MustNewPKIXBase64Key(comid.TestECPubKey), + ), + }, + ) + require.NotNil(t, c) + + tv := NewUnsignedCorim(). + SetID("invalid.tags.corim"). + AddDependentRim("http://endorser.example/addon.corim", nil). + AddProfile("https://arm.com/psa/iot/2.0.0"). + AddComid(*c) + + require.NotNil(t, tv) + + extMap := extensions.NewMap().Add(ExtEntity, &struct{}{}) + err := tv.RegisterExtensions(extMap) + assert.NoError(t, err) + + buf, err := tv.ToCBOR() + assert.NoError(t, err) + + other := NewUnsignedCorim() + err = other.FromCBOR(buf) + assert.NoError(t, err) + + assert.EqualValues(t, tv.Profiles, other.Profiles) + assert.EqualValues(t, tv.ID, other.ID) + assert.Nil(t, other.Entities) +} + +func TestUnsignedCorim_extensions(t *testing.T) { + c := NewUnsignedCorim() + corimExts := struct{}{} + extMap := extensions.NewMap(). + Add(ExtUnsignedCorim, &corimExts). + Add(ExtEntity, &struct{}{}) + + err := c.RegisterExtensions(extMap) + assert.NoError(t, err) + assert.Equal(t, &corimExts, c.GetExtensions()) + + badMap := extensions.NewMap().Add(extensions.Point("test"), &struct{}{}) + err = c.RegisterExtensions(badMap) + assert.EqualError(t, err, `unexpected extension point: "test"`) +} + +func TestLocator_Valid(t *testing.T) { + l := Locator{} + assert.EqualError(t, l.Valid(), "empty href") + + l.Href = comid.TaggedURI("https://example.com") + assert.NoError(t, l.Valid()) + + l.Thumbprint = &swid.HashEntry{} + assert.EqualError(t, l.Valid(), "invalid locator thumbprint: unknown hash algorithm 0") + +} diff --git a/encoding/cbor.go b/encoding/cbor.go index 380442ae..74656bd8 100644 --- a/encoding/cbor.go +++ b/encoding/cbor.go @@ -57,6 +57,10 @@ func doSerializeStructToCBOR( parts := strings.Split(tag, ",") keyString := parts[0] + if keyString == "-" { + continue // field is not marshaled + } + isOmitEmpty := false if len(parts) > 1 { for _, option := range parts[1:] { @@ -142,6 +146,10 @@ func doPopulateStructFromCBOR( parts := strings.Split(tag, ",") keyString := parts[0] + if keyString == "-" { // field is not marshaled + continue + } + isOmitEmpty := false if len(parts) > 1 { for _, option := range parts[1:] { diff --git a/encoding/cbor_test.go b/encoding/cbor_test.go index b509e0ab..f63a9380 100644 --- a/encoding/cbor_test.go +++ b/encoding/cbor_test.go @@ -12,8 +12,9 @@ import ( func Test_PopulateStructFromCBOR_simple(t *testing.T) { type SimpleStruct struct { - FieldOne string `cbor:"0,keyasint,omitempty"` - FieldTwo int `cbor:"1,keyasint"` + FieldOne string `cbor:"0,keyasint,omitempty"` + FieldTwo int `cbor:"1,keyasint"` + IgnoredField int `cbor:"-"` } var v SimpleStruct diff --git a/encoding/json.go b/encoding/json.go index 3cca39c0..c8666764 100644 --- a/encoding/json.go +++ b/encoding/json.go @@ -52,6 +52,10 @@ func doSerializeStructToJSON( parts := strings.Split(tag, ",") key := parts[0] + if key == "-" { + continue // field is not marshaled + } + isOmitEmpty := false if len(parts) > 1 { for _, option := range parts[1:] { @@ -131,6 +135,10 @@ func doPopulateStructFromJSON( parts := strings.Split(tag, ",") key := parts[0] + if key == "-" { + continue // field is not marshaled + } + isOmitEmpty := false if len(parts) > 1 { for _, option := range parts[1:] { diff --git a/encoding/json_test.go b/encoding/json_test.go index d25e0287..33977381 100644 --- a/encoding/json_test.go +++ b/encoding/json_test.go @@ -13,8 +13,9 @@ import ( func Test_PopulateStructFromJSON(t *testing.T) { type SimpleStruct struct { - FieldOne string `json:"field-one,omitempty"` - FieldTwo int `json:"field-two"` + FieldOne string `json:"field-one,omitempty"` + FieldTwo int `json:"field-two"` + IgnoredField int `json:"-"` } var v SimpleStruct diff --git a/extensions/README.md b/extensions/README.md index aeffe0f7..d64dffcd 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -1,5 +1,5 @@ [CoRIM -specification](https://datatracker.ietf.org/doc/draft-ietf-rats-corim/02) +specification](https://datatracker.ietf.org/doc/draft-ietf-rats-corim/04) may be extended by CoRIM Profiles documented in other specifications at well defined points identified in the base CoRIM spec using CDDL extension sockets. CoRIM profiles may @@ -37,32 +37,58 @@ types. This is done via three distinct extension mechanisms: > CoRIM also "imports" CDDL from the CoSWID spec. Some of > these CoSWID CDDL definitions also feature extension sockets. > However, as they are defined in a different spec and are implemented -> in the [`veraison/swid`](https://github.com/veraison/swid) library, they cannot be extended -> using the extension feature provided in the CoRIM library. The extension -> support in CoRIM library is applicable ONLY to CoRIM and CoMID maps and -> type choices +> in the [`veraison/swid`](https://github.com/veraison/swid) library, they +> cannot be extended using the extension feature provided in the CoRIM library. +> The extension support in CoRIM library is applicable ONLY to CoRIM and CoMID +> maps and type choices ## Map Extensions -Map extensions allow extending CoRIM maps with additional keys, effectively -defining new fields for the corresponding structures. In the code base, these -can be identified by the embedded `Extensions` struct. These are - -- `comid.Comid` -- `comid.Entity` -- `comid.FlagsMap` -- `comid.Mval` -- `comid.Triples` -- `corim.Entity` -- `corim.Signer` -- `corim.UnsignedCorim` +Map extensions allow extending CoRIM and CoMID maps with additional keys, +effectively defining new fields for the corresponding structures. In the code +base, these can be identified by the embedded `Extensions` struct. Each +extensible type has a corresponding `extensions.Point`. These are: + +| type | extension point | +| --------------------- | ------------------------------------------------------------- | +| `comid.Comid` | `comid.ExtComid` | +| `comid.Entity` | `comid.ExtEntity` | +| `comid.FlagsMap` | `comid.ExtReferenceValueFlags`, `comid.ExtEndorsedValueFlags` | +| `comid.Mval` | `comid.ExtReferenceValue`, `comid.ExtEndorsedValue` | +| `comid.Triples` | `comid.ExtTriples` | +| `corim.Entity` | `corim.ExtEntity` | +| `corim.Signer` | `corim.ExtSigner` | +| `corim.UnsignedCorim` | `corim.ExtUnsignedCorim` | + +Note that `comid.Mval` and `comid.FlagsMap` are used for both reference values +and endorsed values, which may be extended separately. This is why there are +two extension points associated with each. Additionally, `comid.ExtMval` and +`comid.ExtFlags` also exist when you want to register extensions with a +`comid.Mval` or `comid.Measurment` (and so don't have the context of whether it +will be going into a reference or an endorsed value). + +The diagram below shows a visual representation of where these extension points +originate in the `struct` hierarchy, and which CoRIM object are "aware" of +which extension points: + +![map extensions](corim-map-extensions.png?) + +(note: the diagram shows the logical relationships between structures and does +not meant to accurately reflect the code -- e.g. it omits the container types, +representing them as slices) To extend the above types, you need to define a struct containing your -extensions and pass a pointer to an instance of that struct to the -`RegisterExtensions()` method of the corresponding instance of the type that is -being extended. This should be done as early as possible, before any marshaling -is performed. +extensions for each extension point that you want to extend. You then need to +create an `extensions.Map` that maps the extension points to a pointer to an +instance of the corresponding struct. Finally, you need to pass the map to the +`RegisterExtensions()` method of an instance of the top-most type that is being +extended. For example, if you want to extend the comid, reference values, and +entities, you would create a struct defining the extensions for each, call +`extensions.NewMap()` to create a new map, call `Add()` on the map to add +mappings from the three extension points to pointers to empty instances of your +structs, and, finally, pass the map to `comid.Comid.RegisterExtensions()`. This +should be done as early as possible, before any marshaling is performed. These types can be extended in two ways: by adding additional fields, and by introducing additional constraints over existing fields. @@ -121,6 +147,7 @@ import ( "github.com/google/uuid" "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" ) // the struct containing the extensions @@ -154,7 +181,8 @@ var sampleText = ` func main() { var entity comid.Entity - entity.RegisterExtensions(&EntityExtensions{}) + extMap := extensions.NewMap().Add(comid.ExtEntity, &EntityExtensions{}) + entity.RegisterExtensions(extMap) if err := json.Unmarshal([]byte(sampleText), &entity); err != nil { log.Fatalf("ERROR: %s", err.Error()) @@ -166,11 +194,11 @@ func main() { fmt.Println("validation succeeded") } - // obtain the extension field value via a generic getter + // obtain the extension field value via a generic getter email := entity.Extensions.MustGetString("email") fmt.Printf("entity email: %s\n", email) - // retrive the extensions struct and get value via its field. + // retrive the extensions struct and get value via its field. exts := entity.GetExtensions().(*EntityExtensions) fmt.Printf("also entity email: %s\n", exts.Email) } diff --git a/extensions/cbor.go b/extensions/cbor.go new file mode 100644 index 00000000..03ef31ca --- /dev/null +++ b/extensions/cbor.go @@ -0,0 +1,38 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package extensions + +import ( + cbor "github.com/fxamacker/cbor/v2" +) + +var ( + em, emError = initCBOREncMode() + dm, dmError = initCBORDecMode() +) + +func initCBOREncMode() (en cbor.EncMode, err error) { + encOpt := cbor.EncOptions{ + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + TimeTag: cbor.EncTagRequired, + } + return encOpt.EncMode() +} + +func initCBORDecMode() (dm cbor.DecMode, err error) { + decOpt := cbor.DecOptions{ + IndefLength: cbor.IndefLengthForbidden, + } + return decOpt.DecMode() +} + +func init() { + if emError != nil { + panic(emError) + } + if dmError != nil { + panic(dmError) + } +} diff --git a/extensions/collection.go b/extensions/collection.go new file mode 100644 index 00000000..6f7ded7e --- /dev/null +++ b/extensions/collection.go @@ -0,0 +1,225 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package extensions + +import ( + "encoding/json" + "errors" + "fmt" + + cbor "github.com/fxamacker/cbor/v2" +) + +var ErrUnexpectedPoint = errors.New("unexpected extension point") + +// Point defines an extension point -- a place where a struct defining +// extensions may be attached. There is at least one Point corresponding to +// each struct that embeds Extensions. +type Point string + +// Map is a mapping of extension Point's to IMapValue's (pointers to structs that +// define extension fields. +type Map map[Point]IMapValue + +// NewMap instantiates an empty Map +func NewMap() Map { + return make(Map) +} + +// Add a Point-IMapValue entry to the map. If there is already an IMapValue +// associated with the Point, it will be replaced. +func (o Map) Add(p Point, v IMapValue) Map { + o[p] = v + return o +} + +// IExtensible defines an interface for extensible objects (those that can +// register extensions. +type IExtensible[P any] interface { + RegisterExtensions(exts Map) error + GetExtensions() IMapValue + Valid() error + + *P // this interface must be implemented by a pointer to P +} + +// Collection is a generic container for objects who's pointers implement +// IExtensible. In addition of containing instances of extensible objects, it +// is capable of registering extensions for those instances. +type Collection[P any, I IExtensible[P]] struct { + Values []P + + valueExtensions cache +} + +// NewCollection returns a pointer to a new Collections instance. +func NewCollection[P any, I IExtensible[P]]() *Collection[P, I] { + return &Collection[P, I]{Values: []P{}, valueExtensions: cache{make(Map)}} +} + +// Add a new element to the collection. +func (o *Collection[P, I]) Add(p *P) *Collection[P, I] { + if o != nil && p != nil { + if !o.valueExtensions.IsEmpty() { + var m I = p // #nosec G601 -- not an issue in Go 1.22 + // only register cached extensions if the object does + // not have extensions of its own. + if m.GetExtensions() == nil { + exts := o.valueExtensions.Get() + if err := m.RegisterExtensions(exts); err != nil { + // o.valueExtensions have been validated when + // they were set inside o.RegisterExtensions(), + // and as the field is not exported, could not + // have been modified since. Therefore, this + // cannot fail. + panic(err) + } + } + } + o.Values = append(o.Values, *p) + } + + return o +} + +// Clear empties the collection of its values. This does _not_ unregister +// extensions. +func (o *Collection[P, I]) Clear() { + // Setting Values to an empty array rather then nil to ensure correct + // marshaling. + o.Values = []P{} +} + +// GetExtensions returns the extensions IMapValue that has been registered with +// the collection. +func (o *Collection[P, I]) GetExtensions() IMapValue { + return o.valueExtensions.Get() +} + +// Valid returns an error if the collection is invalid, i.e. if it is empty or +// if any of its contents are invalid. +func (o Collection[P, I]) Valid() error { + for i, p := range o.Values { + var m I = &p // #nosec G601 -- not an issue in Go 1.22 + if err := m.Valid(); err != nil { + return fmt.Errorf("error at index %d: %w", i, err) + } + } + + return nil +} + +// IsEmpty return true if the collection is empty, i.e. if it does not contain +// any values. This does not indicate whether or not any extensions have been +// registered with the collection. +func (o Collection[P, I]) IsEmpty() bool { + return len(o.Values) == 0 +} + +// RegisterExtensions register extensions for the collection's values. An error +// is returned if the provided map contains extension points not supported by +// the collection's values. +func (o *Collection[P, I]) RegisterExtensions(exts Map) error { + // validate the provided extensions by creating a new instanced of the + // contained object and attempting to register them with it. + var m I = new(P) + if err := m.RegisterExtensions(exts); err != nil { + return err + } + + o.valueExtensions.Set(exts) + + return nil +} + +func (o Collection[P, I]) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.Values) +} + +func (o *Collection[P, I]) UnmarshalCBOR(data []byte) error { + var rawVals []cbor.RawMessage + + if err := cbor.Unmarshal(data, &rawVals); err != nil { + return err + } + + vals := make([]P, len(rawVals)) + + for i, rv := range rawVals { + var m I = new(P) + + if !o.valueExtensions.IsEmpty() { + if err := m.RegisterExtensions(o.valueExtensions.Get()); err != nil { + return err + } + } + + if err := cbor.Unmarshal(rv, m); err != nil { + return fmt.Errorf("error at index %d: %w", i, err) + } + + vals[i] = *m + } + + o.Values = vals + + return nil +} + +func (o Collection[P, I]) MarshalJSON() ([]byte, error) { + ret, err := json.Marshal(o.Values) + return ret, err +} + +func (o *Collection[P, I]) UnmarshalJSON(data []byte) error { + var rawVals []json.RawMessage + + if err := json.Unmarshal(data, &rawVals); err != nil { + return err + } + + vals := make([]P, len(rawVals)) + + for i, rv := range rawVals { + var m I = new(P) + + if !o.valueExtensions.IsEmpty() { + if err := m.RegisterExtensions(o.valueExtensions.Get()); err != nil { + return fmt.Errorf("could not register extensions: %w", err) + } + } + + if err := json.Unmarshal(rv, m); err != nil { + return fmt.Errorf("error at index %d: %w", i, err) + } + + vals[i] = *m + } + + o.Values = vals + + return nil +} + +type cache struct { + extensions Map +} + +func (o *cache) Set(exts Map) { + o.extensions = exts +} + +func (o cache) Get() Map { + res := make(Map) + + for p, v := range o.extensions { + res[p] = newIMapValue(v) + } + + return res +} + +func (o cache) IsEmpty() bool { + return len(o.extensions) == 0 +} diff --git a/extensions/collection_test.go b/extensions/collection_test.go new file mode 100644 index 00000000..3af3b2b2 --- /dev/null +++ b/extensions/collection_test.go @@ -0,0 +1,170 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package extensions + +import ( + "encoding/json" + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/encoding" +) + +type testExtensible struct { + FieldOne string `cbor:"-1,keyasint,omitempty" json:"field-one,omitempty"` + Error error `cbor:"-" json:"-"` + + Extensions +} + +const testPoint Point = "test" + +func (o *testExtensible) RegisterExtensions(exts Map) error { + for p, v := range exts { + switch p { + case testPoint: + o.Extensions.Register(v) + default: + return fmt.Errorf("%w: %q", ErrUnexpectedPoint, p) + } + } + + return nil +} + +func (o testExtensible) GetExtensions() IMapValue { + return o.Extensions.IMapValue +} + +func (o testExtensible) Valid() error { + return o.Error +} + +func (o *testExtensible) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +func (o testExtensible) MarshalCBOR() ([]byte, error) { + return encoding.SerializeStructToCBOR(em, o) +} + +func (o *testExtensible) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +func (o testExtensible) MarshalJSON() ([]byte, error) { + return encoding.SerializeStructToJSON(o) +} + +type testExtensions struct { + FieldTwo string `cbor:"-2,keyasint,omitempty" json:"field-two,omitempty"` +} + +func Test_Collection_JSON(t *testing.T) { + testData := []byte(` + [ + { + "field-one": "foo", + "field-two": "bar" + }, + { + "field-one": "buzz" + } + ] + `) + + c := NewCollection[testExtensible]() + + err := json.Unmarshal(testData, c) + require.NoError(t, err) + + // as we've not registred the extensions, the unknown field will be ignored. + decoded, err := c.Values[0].Extensions.GetString("field-two") + assert.Equal(t, "", decoded) + assert.EqualError(t, err, "extension not found: field-two") + + err = c.RegisterExtensions(Map{testPoint: &testExtensions{}}) + require.NoError(t, err) + c.Clear() // clear only clears the values array, it does not "unregister" extensions. + + err = json.Unmarshal(testData, c) + require.NoError(t, err) + + decoded, err = c.Values[0].Extensions.GetString("field-two") + require.NoError(t, err) + assert.Equal(t, "bar", decoded) + + data, err := json.Marshal(c) + require.NoError(t, err) + assert.JSONEq(t, string(testData), string(data)) +} + +func Test_Collection_CBOR(t *testing.T) { + testData := []byte{ + 0x82, 0xa2, 0x20, 0x63, 0x66, 0x6f, 0x6f, 0x21, 0x63, 0x62, + 0x61, 0x72, 0xa1, 0x20, 0x64, 0x62, 0x75, 0x7a, 0x7a, + } + + c := NewCollection[testExtensible]() + + err := dm.Unmarshal(testData, c) + require.NoError(t, err) + + // as we've not registred the extensions, the unknown field will be ignored. + decoded, err := c.Values[0].Extensions.GetString("field-two") + assert.Equal(t, "", decoded) + assert.EqualError(t, err, "extension not found: field-two") + + err = c.RegisterExtensions(Map{testPoint: &testExtensions{}}) + require.NoError(t, err) + c.Clear() // clear only clears the values array, it does not "unregister" extensions. + + err = dm.Unmarshal(testData, c) + require.NoError(t, err) + + decoded, err = c.Values[0].Extensions.GetString("field-two") + require.NoError(t, err) + assert.Equal(t, "bar", decoded) + + data, err := c.MarshalCBOR() + require.NoError(t, err) + assert.Equal(t, testData, data) +} + +func Test_Collection_Add(t *testing.T) { + c := NewCollection[testExtensible]() + + assert.True(t, c.IsEmpty()) + assert.Len(t, c.Values, 0) + + e := testExtensible{} + c.Add(&e) + + assert.False(t, c.IsEmpty()) + assert.Len(t, c.Values, 1) + assert.Equal(t, e, c.Values[0]) +} + +func Test_Collection_Valid(t *testing.T) { + c := NewCollection[testExtensible]() + assert.NoError(t, c.Valid()) + + c.Add(&testExtensible{}) + assert.NoError(t, c.Valid()) + + c.Add(&testExtensible{Error: errors.New("test error")}) + assert.EqualError(t, c.Valid(), "error at index 1: test error") +} + +func Test_Map(t *testing.T) { + m := NewMap() + assert.Equal(t, 0, len(m)) + + other := m.Add(testPoint, &testExtensible{}) + assert.Equal(t, 1, len(m)) + assert.Equal(t, m, other) +} diff --git a/extensions/corim-map-extensions.png b/extensions/corim-map-extensions.png new file mode 100644 index 0000000000000000000000000000000000000000..57aa2f245602a5849730ad4978cc84ec22d3136e GIT binary patch literal 249791 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 E0Ho`+cmMzZ literal 0 HcmV?d00001 diff --git a/extensions/extensions.go b/extensions/extensions.go index 3362752f..2912d132 100644 --- a/extensions/extensions.go +++ b/extensions/extensions.go @@ -13,31 +13,35 @@ import ( var ErrExtensionNotFound = errors.New("extension not found") -type IExtensionsValue any +type IMapValue any type Extensions struct { - IExtensionsValue `json:"extensions,omitempty"` + IMapValue `json:"extensions,omitempty"` } -func (o *Extensions) Register(exts IExtensionsValue) { +func (o *Extensions) Register(exts IMapValue) { if reflect.TypeOf(exts).Kind() != reflect.Pointer { - panic("attempting to register a non-pointer IExtensionsValue") + panic("attempting to register a non-pointer IMapValue") } - o.IExtensionsValue = exts + o.IMapValue = exts } func (o *Extensions) HaveExtensions() bool { - return o.IExtensionsValue != nil + return o.IMapValue != nil +} + +func (o Extensions) New() IMapValue { + return newIMapValue(o.IMapValue) } func (o *Extensions) Get(name string) (any, error) { - if o.IExtensionsValue == nil { + if o.IMapValue == nil { return nil, fmt.Errorf("%w: %s", ErrExtensionNotFound, name) } - extType := reflect.TypeOf(o.IExtensionsValue) - extVal := reflect.ValueOf(o.IExtensionsValue) + extType := reflect.TypeOf(o.IMapValue) + extVal := reflect.ValueOf(o.IMapValue) if extType.Kind() == reflect.Pointer { extType = extType.Elem() extVal = extVal.Elem() @@ -66,6 +70,25 @@ func (o *Extensions) Get(name string) (any, error) { return nil, fmt.Errorf("%w: %s", ErrExtensionNotFound, name) } +func (o *Extensions) IsEmpty() bool { + if o.IMapValue == nil { + return true + } + + extVal := reflect.ValueOf(o.IMapValue) + if reflect.TypeOf(o.IMapValue).Kind() == reflect.Pointer { + extVal = extVal.Elem() + } + + for i := 0; i < extVal.NumField(); i++ { + if !extVal.Field(i).IsZero() { + return false + } + } + + return true +} + func (o *Extensions) MustGetString(name string) string { v, _ := o.GetString(name) return v @@ -333,12 +356,12 @@ func (o *Extensions) GetStringMapString(name string) (map[string]string, error) } func (o *Extensions) Set(name string, value any) error { - if o.IExtensionsValue == nil { + if o.IMapValue == nil { return fmt.Errorf("%w: %s", ErrExtensionNotFound, name) } - extType := reflect.TypeOf(o.IExtensionsValue) - extVal := reflect.ValueOf(o.IExtensionsValue) + extType := reflect.TypeOf(o.IMapValue) + extVal := reflect.ValueOf(o.IMapValue) if extType.Kind() == reflect.Pointer { extType = extType.Elem() extVal = extVal.Elem() @@ -377,3 +400,13 @@ func (o *Extensions) Set(name string, value any) error { return fmt.Errorf("%w: %s", ErrExtensionNotFound, name) } + +func newIMapValue(v IMapValue) IMapValue { + if v == nil { + return nil + } + + valType := reflect.Indirect(reflect.ValueOf(v)).Type() + + return reflect.New(valType).Interface() +} diff --git a/extensions/extensions_test.go b/extensions/extensions_test.go index 901bbe6a..11ba985d 100644 --- a/extensions/extensions_test.go +++ b/extensions/extensions_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type Entity struct { @@ -52,7 +53,7 @@ func TestExtensions_GetSet(t *testing.T) { "Lisa": "elementary school student", }, } - exts := Extensions{IExtensionsValue: &extsVal} + exts := Extensions{IMapValue: &extsVal} v, err := exts.GetInt("size") assert.NoError(t, err) @@ -120,3 +121,31 @@ func TestExtensions_GetSet(t *testing.T) { assert.Equal(t, "", exts.MustGetString("does-not-exist")) assert.Equal(t, 0, exts.MustGetInt("does-not-exist")) } + +func Test_Extensions_New(t *testing.T) { + exts := Extensions{} + + assert.Nil(t, exts.New()) + + exts.Register(&TestExtensions{}) + + newValOne, ok := exts.New().(*TestExtensions) + require.True(t, ok) + + newValTwo, ok := exts.New().(*TestExtensions) + require.True(t, ok) + + newValOne.Address = "123 Fake Street" + assert.Equal(t, "", newValTwo.Address) +} + +func Test_Extensions_IsEmpty(t *testing.T) { + exts := Extensions{} + assert.True(t, exts.IsEmpty()) + + exts.Register(&TestExtensions{}) + assert.True(t, exts.IsEmpty()) + + exts.Register(&TestExtensions{Address: "123 Fake Street"}) + assert.False(t, exts.IsEmpty()) +} diff --git a/extensions/typechoice_test.go b/extensions/typechoice_test.go new file mode 100644 index 00000000..c0e9ac62 --- /dev/null +++ b/extensions/typechoice_test.go @@ -0,0 +1,29 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package extensions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type testTCValue string + +func (o testTCValue) String() string { + return string(o) +} + +func (o testTCValue) Valid() error { + return nil +} + +func (o testTCValue) Type() string { + return "test_type" +} + +func Test_TypeChoiceValueMarshalJSON(t *testing.T) { + buf, err := TypeChoiceValueMarshalJSON(testTCValue("test")) + assert.NoError(t, err) + assert.JSONEq(t, `{"type": "test_type", "value": "test"}`, string(buf)) +} diff --git a/go.mod b/go.mod index ef17b853..33c4d26e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/veraison/corim -go 1.19 +go 1.22 require ( github.com/fxamacker/cbor/v2 v2.5.0 From aba96790544a709660fdf6d9d785975464a542de Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 10 Jul 2024 09:34:16 +0100 Subject: [PATCH 042/110] refactor!: merge key triple types into KeyTriple Replace DevIdentityKey and AttestVerifKey with KeyTriple type that is used for both. The two types were identical. They were the same structure being used for two purposes. The purposes are already reflected in the field names within Triples structure, and there is no need to also include it in the type name, which only leads to unnecessary code duplication. Additionally, the old names were misleading as they implied that the types represented a single key, whereas they in fact contained a collection of keys. Finally, Alias []KeyTriple to KeyTriples type. This aligns with how collections are handled elsewhere and provides a more consistent API. Signed-off-by: Sergei Trofimov --- comid/attestverifkey.go | 29 ------------- comid/attestverifkey_test.go | 42 ------------------- comid/comid.go | 8 ++-- comid/example_psa_keys_test.go | 2 +- comid/example_test.go | 8 ++-- comid/{devidentitykey.go => keytriple.go} | 14 +++++-- ...videntitykey_test.go => keytriple_test.go} | 6 +-- comid/triples.go | 12 +++--- comid/triples_test.go | 4 +- corim/unsignedcorim_test.go | 6 +-- 10 files changed, 33 insertions(+), 98 deletions(-) delete mode 100644 comid/attestverifkey.go delete mode 100644 comid/attestverifkey_test.go rename comid/{devidentitykey.go => keytriple.go} (69%) rename comid/{devidentitykey_test.go => keytriple_test.go} (82%) diff --git a/comid/attestverifkey.go b/comid/attestverifkey.go deleted file mode 100644 index b8e86a05..00000000 --- a/comid/attestverifkey.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2021-2023 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import ( - "fmt" -) - -// AttestVerifKey stores an attest-key-triple-record with CBOR and JSON -// serializations. Note that the CBOR serialization packs the structure into an -// array. Instead, when serializing to JSON, the structure is converted into an -// object. -type AttestVerifKey struct { - _ struct{} `cbor:",toarray"` - Environment Environment `json:"environment"` - VerifKeys CryptoKeys `json:"verification-keys"` -} - -func (o AttestVerifKey) Valid() error { - - if err := o.Environment.Valid(); err != nil { - return fmt.Errorf("environment validation failed: %w", err) - } - if err := o.VerifKeys.Valid(); err != nil { - return fmt.Errorf("verification keys validation failed: %w", err) - } - return nil -} diff --git a/comid/attestverifkey_test.go b/comid/attestverifkey_test.go deleted file mode 100644 index 20c791d9..00000000 --- a/comid/attestverifkey_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2021-2023 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAttestVerifKey_Valid_empty(t *testing.T) { - invalidKey := CryptoKey{TaggedPKIXBase64Key("")} - - tvs := []struct { - env Environment - verifkey CryptoKeys - testerr string - }{ - { - env: Environment{}, - verifkey: CryptoKeys{}, - testerr: "environment validation failed: environment must not be empty", - }, - { - env: Environment{Instance: MustNewUEIDInstance(TestUEID)}, - verifkey: CryptoKeys{}, - testerr: "verification keys validation failed: no keys to validate", - }, - { - env: Environment{Instance: MustNewUEIDInstance(TestUEID)}, - verifkey: CryptoKeys{&invalidKey}, - testerr: "verification keys validation failed: invalid key at index 0: key value not set", - }, - } - - for _, tv := range tvs { - av := AttestVerifKey{Environment: tv.env, VerifKeys: tv.verifkey} - err := av.Valid() - assert.EqualError(t, err, tv.testerr) - } -} diff --git a/comid/comid.go b/comid/comid.go index 7b075a5f..1d3a5a33 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -214,10 +214,10 @@ func (o *Comid) AddEndorsedValue(val ValueTriple) *Comid { // AddAttestVerifKey adds the supplied endorsed value to the // attest-key-triples list of the target Comid. -func (o *Comid) AddAttestVerifKey(val AttestVerifKey) *Comid { +func (o *Comid) AddAttestVerifKey(val KeyTriple) *Comid { if o != nil { if o.Triples.AttestVerifKeys == nil { - o.Triples.AttestVerifKeys = new([]AttestVerifKey) + o.Triples.AttestVerifKeys = NewKeyTriples() } if o.Triples.AddAttestVerifKey(val) == nil { @@ -229,10 +229,10 @@ func (o *Comid) AddAttestVerifKey(val AttestVerifKey) *Comid { // AddDevIdentityKey adds the supplied identity key to the // identity-triples list of the target Comid. -func (o *Comid) AddDevIdentityKey(val DevIdentityKey) *Comid { +func (o *Comid) AddDevIdentityKey(val KeyTriple) *Comid { if o != nil { if o.Triples.DevIdentityKeys == nil { - o.Triples.DevIdentityKeys = new([]DevIdentityKey) + o.Triples.DevIdentityKeys = NewKeyTriples() } if o.Triples.AddDevIdentityKey(val) == nil { diff --git a/comid/example_psa_keys_test.go b/comid/example_psa_keys_test.go index 6b2d8d5a..b65216d8 100644 --- a/comid/example_psa_keys_test.go +++ b/comid/example_psa_keys_test.go @@ -43,7 +43,7 @@ func extractKeys(c *Comid) error { return nil } -func extractPSAKey(k AttestVerifKey) error { +func extractPSAKey(k KeyTriple) error { class := k.Environment.Class if err := extractImplementationID(class); err != nil { diff --git a/comid/example_test.go b/comid/example_test.go index 1c8466e2..08b040ff 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -75,7 +75,7 @@ func Example_encode() { }, ). AddAttestVerifKey( - AttestVerifKey{ + KeyTriple{ Environment: Environment{ Instance: MustNewUUIDInstance(uuid.UUID(TestUUID)), }, @@ -85,7 +85,7 @@ func Example_encode() { ), }, ).AddDevIdentityKey( - DevIdentityKey{ + KeyTriple{ Environment: Environment{ Instance: MustNewUEIDInstance(TestUEID), }, @@ -138,7 +138,7 @@ func Example_encode_PSA() { }, ). AddAttestVerifKey( - AttestVerifKey{ + KeyTriple{ Environment: Environment{ Instance: MustNewUEIDInstance(TestUEID), }, @@ -169,7 +169,7 @@ func Example_encode_PSA_attestation_verification() { SetTagIdentity("my-ns:acme-roadrunner-supplement", 0). AddEntity("ACME Ltd.", &TestRegID, RoleCreator, RoleTagCreator, RoleMaintainer). AddAttestVerifKey( - AttestVerifKey{ + KeyTriple{ Environment: Environment{ Instance: MustNewUEIDInstance(TestUEID), }, diff --git a/comid/devidentitykey.go b/comid/keytriple.go similarity index 69% rename from comid/devidentitykey.go rename to comid/keytriple.go index 3d3bf7a4..f356e9ad 100644 --- a/comid/devidentitykey.go +++ b/comid/keytriple.go @@ -1,21 +1,21 @@ -// Copyright 2021-2023 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid import "fmt" -// DevIdentityKey stores an identity-triple-record with CBOR and JSON +// KeyTriple stores an identity-triple-record with CBOR and JSON // serializations. Note that the CBOR serialization packs the structure into an // array. Instead, when serializing to JSON, the structure is converted into an // object. -type DevIdentityKey struct { +type KeyTriple struct { _ struct{} `cbor:",toarray"` Environment Environment `json:"environment"` VerifKeys CryptoKeys `json:"verification-keys"` } -func (o DevIdentityKey) Valid() error { +func (o KeyTriple) Valid() error { if err := o.Environment.Valid(); err != nil { return fmt.Errorf("environment validation failed: %w", err) } @@ -25,3 +25,9 @@ func (o DevIdentityKey) Valid() error { } return nil } + +type KeyTriples []KeyTriple + +func NewKeyTriples() *KeyTriples { + return &KeyTriples{} +} diff --git a/comid/devidentitykey_test.go b/comid/keytriple_test.go similarity index 82% rename from comid/devidentitykey_test.go rename to comid/keytriple_test.go index b12f6a41..334ad4fe 100644 --- a/comid/devidentitykey_test.go +++ b/comid/keytriple_test.go @@ -1,4 +1,4 @@ -// Copyright 2021-2023 Contributors to the Veraison project. +// Copyright 2021-2024 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestDevIdentityKey_Valid_empty(t *testing.T) { +func TestVerificationKeys_Valid_empty(t *testing.T) { invalidKey := CryptoKey{TaggedPKIXBase64Key("")} tvs := []struct { @@ -34,7 +34,7 @@ func TestDevIdentityKey_Valid_empty(t *testing.T) { }, } for _, tv := range tvs { - av := DevIdentityKey{Environment: tv.env, VerifKeys: tv.verifkey} + av := KeyTriple{Environment: tv.env, VerifKeys: tv.verifkey} err := av.Valid() assert.EqualError(t, err, tv.testerr) } diff --git a/comid/triples.go b/comid/triples.go index d0327422..622a2a4a 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -11,10 +11,10 @@ import ( ) type Triples struct { - ReferenceValues *ValueTriples `cbor:"0,keyasint,omitempty" json:"reference-values,omitempty"` - EndorsedValues *ValueTriples `cbor:"1,keyasint,omitempty" json:"endorsed-values,omitempty"` - AttestVerifKeys *[]AttestVerifKey `cbor:"2,keyasint,omitempty" json:"attester-verification-keys,omitempty"` - DevIdentityKeys *[]DevIdentityKey `cbor:"3,keyasint,omitempty" json:"dev-identity-keys,omitempty"` + ReferenceValues *ValueTriples `cbor:"0,keyasint,omitempty" json:"reference-values,omitempty"` + EndorsedValues *ValueTriples `cbor:"1,keyasint,omitempty" json:"endorsed-values,omitempty"` + AttestVerifKeys *KeyTriples `cbor:"2,keyasint,omitempty" json:"attester-verification-keys,omitempty"` + DevIdentityKeys *KeyTriples `cbor:"3,keyasint,omitempty" json:"dev-identity-keys,omitempty"` Extensions } @@ -182,7 +182,7 @@ func (o *Triples) AddEndorsedValue(val ValueTriple) *Triples { return o } -func (o *Triples) AddAttestVerifKey(val AttestVerifKey) *Triples { +func (o *Triples) AddAttestVerifKey(val KeyTriple) *Triples { if o != nil { *o.AttestVerifKeys = append(*o.AttestVerifKeys, val) } @@ -190,7 +190,7 @@ func (o *Triples) AddAttestVerifKey(val AttestVerifKey) *Triples { return o } -func (o *Triples) AddDevIdentityKey(val DevIdentityKey) *Triples { +func (o *Triples) AddDevIdentityKey(val KeyTriple) *Triples { if o != nil { *o.DevIdentityKeys = append(*o.DevIdentityKeys, val) } diff --git a/comid/triples_test.go b/comid/triples_test.go index e5183827..62e21e21 100644 --- a/comid/triples_test.go +++ b/comid/triples_test.go @@ -67,12 +67,12 @@ func TestTriples_Valid(t *testing.T) { assert.EqualError(t, err, "endorsed values: error at index 0: environment validation failed: environment must not be empty") triples.EndorsedValues = nil - triples.AttestVerifKeys = &[]AttestVerifKey{{}} + triples.AttestVerifKeys = &KeyTriples{{}} err = triples.Valid() assert.EqualError(t, err, "attestation verification key at index 0: environment validation failed: environment must not be empty") triples.AttestVerifKeys = nil - triples.DevIdentityKeys = &[]DevIdentityKey{{}} + triples.DevIdentityKeys = &KeyTriples{{}} err = triples.Valid() assert.EqualError(t, err, "device identity key at index 0: environment validation failed: environment must not be empty") } diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index b9b6b979..72dac2e5 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -180,7 +180,7 @@ func TestUnsignedCorim_Valid_ok(t *testing.T) { c := comid.NewComid(). SetTagIdentity("vendor.example/prod/1", 0). AddAttestVerifKey( - comid.AttestVerifKey{ + comid.KeyTriple{ Environment: comid.Environment{ Instance: comid.MustNewUUIDInstance(comid.TestUUID), }, @@ -314,7 +314,7 @@ func TestUnsignedCorim_ToJSON(t *testing.T) { c := comid.NewComid(). SetTagIdentity("vendor.example/prod/1", 0). AddAttestVerifKey( - comid.AttestVerifKey{ + comid.KeyTriple{ Environment: comid.Environment{ Instance: comid.MustNewUUIDInstance(comid.TestUUID), }, @@ -357,7 +357,7 @@ func TestUnsignedCorim_ToCBOR(t *testing.T) { c := comid.NewComid(). SetTagIdentity("vendor.example/prod/1", 0). AddAttestVerifKey( - comid.AttestVerifKey{ + comid.KeyTriple{ Environment: comid.Environment{ Instance: comid.MustNewUUIDInstance(comid.TestUUID), }, From 13838cadc7891590edcd65a71ec1a127446911c3 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 10 Jul 2024 10:25:54 +0100 Subject: [PATCH 043/110] fix: swap DevIdentityKeys and AttestVerifKeys Align the CBOR keys of DevIdentityKeys an AttestVerifKeys with the spec. As per the latest [spec][1], identity-triples are under index 2 and attest-key-triples are under index 3 in the triples-map. For whatever reason, they were swapped in our Triples structure. [1]: https://www.ietf.org/archive/id/draft-ietf-rats-corim-04.html#name-device-identity-triple Signed-off-by: Sergei Trofimov --- comid/comid.go | 2 +- comid/example_test.go | 12 ++++++------ comid/keytriple.go | 8 ++++---- comid/triples.go | 4 ++-- corim/unsignedcorim_test.go | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/comid/comid.go b/comid/comid.go index 1d3a5a33..f01108f2 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -212,7 +212,7 @@ func (o *Comid) AddEndorsedValue(val ValueTriple) *Comid { return o } -// AddAttestVerifKey adds the supplied endorsed value to the +// AddAttestVerifKey adds the supplied verification key to the // attest-key-triples list of the target Comid. func (o *Comid) AddAttestVerifKey(val KeyTriple) *Comid { if o != nil { diff --git a/comid/example_test.go b/comid/example_test.go index 08b040ff..d94b8fa8 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -107,8 +107,8 @@ func Example_encode() { } // Output: - // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + //a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA() { @@ -160,8 +160,8 @@ func Example_encode_PSA() { } // Output: - // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA_attestation_verification() { @@ -191,8 +191,8 @@ func Example_encode_PSA_attestation_verification() { } // Output: - // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a1028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a1038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_decode_JSON() { diff --git a/comid/keytriple.go b/comid/keytriple.go index f356e9ad..9f9a03fb 100644 --- a/comid/keytriple.go +++ b/comid/keytriple.go @@ -5,10 +5,10 @@ package comid import "fmt" -// KeyTriple stores an identity-triple-record with CBOR and JSON -// serializations. Note that the CBOR serialization packs the structure into an -// array. Instead, when serializing to JSON, the structure is converted into an -// object. +// KeyTriple stores a cryptographic key triple record (identity-triple-record +// or attest-key-triple-record) with CBOR and JSON serializations. Note that +// the CBOR serialization packs the structure into an array. Instead, when +// serializing to JSON, the structure is converted into an object. type KeyTriple struct { _ struct{} `cbor:",toarray"` Environment Environment `json:"environment"` diff --git a/comid/triples.go b/comid/triples.go index 622a2a4a..7348dace 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -13,8 +13,8 @@ import ( type Triples struct { ReferenceValues *ValueTriples `cbor:"0,keyasint,omitempty" json:"reference-values,omitempty"` EndorsedValues *ValueTriples `cbor:"1,keyasint,omitempty" json:"endorsed-values,omitempty"` - AttestVerifKeys *KeyTriples `cbor:"2,keyasint,omitempty" json:"attester-verification-keys,omitempty"` - DevIdentityKeys *KeyTriples `cbor:"3,keyasint,omitempty" json:"dev-identity-keys,omitempty"` + DevIdentityKeys *KeyTriples `cbor:"2,keyasint,omitempty" json:"dev-identity-keys,omitempty"` + AttestVerifKeys *KeyTriples `cbor:"3,keyasint,omitempty" json:"attester-verification-keys,omitempty"` Extensions } diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 72dac2e5..e3f0a4df 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -344,7 +344,7 @@ func TestUnsignedCorim_ToJSON(t *testing.T) { expectedJSON := ` { "corim-id":"invalid.tags.corim", - "tags":["2QH6ogGhAHV2ZW5kb3IuZXhhbXBsZS9wcm9kLzEEoQKBgqEB2CVQMftavwI+SZKqTpX5wVA7+oHZAip4sS0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tCk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVcxQnZxRisvcnk4QldhN1pFTVUxeFlZSEVROEIKbExUNE1GSE9hTytJQ1R0SXZyRWVFcHIvc2ZUQVA2NkgyaENIZGI1SEVYS3RSS29kNlFMY09MUEExUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ=="], + "tags":["2QH6ogGhAHV2ZW5kb3IuZXhhbXBsZS9wcm9kLzEEoQOBgqEB2CVQMftavwI+SZKqTpX5wVA7+oHZAip4sS0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tCk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVcxQnZxRisvcnk4QldhN1pFTVUxeFlZSEVROEIKbExUNE1GSE9hTytJQ1R0SXZyRWVFcHIvc2ZUQVA2NkgyaENIZGI1SEVYS3RSS29kNlFMY09MUEExUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ=="], "dependent-rims":[{"href":"http://endorser.example/addon.corim"}], "profiles":["https://arm.com/psa/iot/2.0.0"] } From 37e8df3e5153ba9fb1bbf1c5d6bce7c835bf8a79 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 11 Jul 2024 15:07:30 +0100 Subject: [PATCH 044/110] fix: only one eat.Profile in UnsignedCorim Bring our implementation in line with the recent [corim spec][1] by replacing Profiles []eat.Profile field inside UnsignedCorim with a single profile (multiple profiles have not been allowed since revision 00). This change required updating test cases that relied on hard-coded CBOR binary dumps. To make this easier in the future, allow generating them from YAML descriptions. The test cases are now kept inside external CBOR files rather than hard-coded in the source (Go's embed mechanism is used to allow test code to remain the same), along with YAML sources. A script is provided to re-generate the CBOR from YAML. In the future, when the structures change, YAML source can be edited to reflect the change, and the script then used to re-generate the CBOR. The script relies on an external tool (which the script will install if its not already present): https://github.com/veraison/gen-testcases note: YAML is used rather than JSON (which we already use for cocli "templates") because it provides native support for binary strings/arrays. JSON has no mechanism to distinguish between a string that is a string, and a string that is a base64 encoding of binary data. For cocli, the type of the struct field being unmarshaled is used to make the distinction. For these test cases we do not want to rely on pre-defined structs, and so conversion is done using only types native to the formats, hence YAML's support for byte data via !!binary tag is essential. [1]: https://datatracker.ietf.org/doc/html/draft-ietf-rats-corim-04 Signed-off-by: Sergei Trofimov --- cocli/cmd/test_vars.go | 275 ++---------------- cocli/cmd/testcases/ec-p256.jwk | 9 + cocli/cmd/testcases/psa-refval-orig.cbor | Bin 0 -> 416 bytes cocli/cmd/testcases/psa-refval.cbor | Bin 0 -> 417 bytes cocli/cmd/testcases/regen-from-src.sh | 40 +++ cocli/cmd/testcases/signed-corim-invalid.cbor | Bin 0 -> 166 bytes .../signed-corim-valid-with-cots.cbor | Bin 0 -> 2349 bytes cocli/cmd/testcases/signed-corim-valid.cbor | Bin 0 -> 799 bytes cocli/cmd/testcases/src/psa-refval.yaml | 60 ++++ .../src/signed-corim-invalid-meta.yaml | 13 + .../testcases/src/signed-corim-invalid.yaml | 1 + .../src/signed-corim-valid-meta.yaml | 13 + .../signed-corim-valid-with-cots-meta.yaml | 3 + .../src/signed-corim-valid-with-cots.yaml | 6 + .../cmd/testcases/src/signed-corim-valid.yaml | 87 ++++++ cocli/cmd/testcases/src/test-comid.yaml | 60 ++++ cocli/cmd/testcases/src/test-coswid.yaml | 35 +++ cocli/cmd/testcases/src/test-cots.yaml | 17 ++ cocli/cmd/testcases/test-comid.cbor | Bin 0 -> 417 bytes cocli/cmd/testcases/test-coswid.cbor | Bin 0 -> 377 bytes cocli/cmd/testcases/test-cots.cbor | Bin 0 -> 1943 bytes .../data/corim/templates/corim-cca-realm.json | 6 +- cocli/data/corim/templates/corim-cca.json | 6 +- cocli/data/corim/templates/corim-full.json | 4 +- corim/unsignedcorim.go | 31 +- corim/unsignedcorim_test.go | 10 +- 26 files changed, 393 insertions(+), 283 deletions(-) create mode 100644 cocli/cmd/testcases/ec-p256.jwk create mode 100644 cocli/cmd/testcases/psa-refval-orig.cbor create mode 100644 cocli/cmd/testcases/psa-refval.cbor create mode 100644 cocli/cmd/testcases/regen-from-src.sh create mode 100644 cocli/cmd/testcases/signed-corim-invalid.cbor create mode 100644 cocli/cmd/testcases/signed-corim-valid-with-cots.cbor create mode 100644 cocli/cmd/testcases/signed-corim-valid.cbor create mode 100644 cocli/cmd/testcases/src/psa-refval.yaml create mode 100644 cocli/cmd/testcases/src/signed-corim-invalid-meta.yaml create mode 100644 cocli/cmd/testcases/src/signed-corim-invalid.yaml create mode 100644 cocli/cmd/testcases/src/signed-corim-valid-meta.yaml create mode 100644 cocli/cmd/testcases/src/signed-corim-valid-with-cots-meta.yaml create mode 100644 cocli/cmd/testcases/src/signed-corim-valid-with-cots.yaml create mode 100644 cocli/cmd/testcases/src/signed-corim-valid.yaml create mode 100644 cocli/cmd/testcases/src/test-comid.yaml create mode 100644 cocli/cmd/testcases/src/test-coswid.yaml create mode 100644 cocli/cmd/testcases/src/test-cots.yaml create mode 100644 cocli/cmd/testcases/test-comid.cbor create mode 100644 cocli/cmd/testcases/test-coswid.cbor create mode 100644 cocli/cmd/testcases/test-cots.cbor diff --git a/cocli/cmd/test_vars.go b/cocli/cmd/test_vars.go index 0a8cdad3..99b6ee9a 100644 --- a/cocli/cmd/test_vars.go +++ b/cocli/cmd/test_vars.go @@ -3,7 +3,11 @@ package cmd -import "github.com/veraison/corim/comid" +import ( + _ "embed" + + "github.com/veraison/corim/comid" +) var ( minimalCorimTemplate = []byte(`{ @@ -35,255 +39,28 @@ var ( "not-after": "2025-12-31T00:00:00Z" } }`) - testECKey = []byte(`{ - "kty": "EC", - "crv": "P-256", - "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", - "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", - "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", - "use": "enc", - "kid": "1" - }`) - testSignedCorimValid = comid.MustHexDecode(nil, ` - d284585da3012603746170706c69636174696f6e2f72696d2b63626f7208 - 5841a200a2007441434d45204c7464207369676e696e67206b657901d820 - 7468747470733a2f2f61636d652e6578616d706c6501a200c11a61ce4800 - 01c11a69546780a0590282a600505c57e8f446cd421b91c908cf93e13cfc - 01815901a3d901faa40065656e2d474201a1005043bbe37f2e614b33aed3 - 53cff1428b160281a3006941434d45204c74642e01d8207468747470733a - 2f2f61636d652e6578616d706c65028300010204a1008182a100a300d902 - 58582061636d652d696d706c656d656e746174696f6e2d69642d30303030 - 3030303031016441434d45026a526f616452756e6e657283a200d90259a3 - 0162424c0465322e312e30055820acbb11c7e4da217205523ce4ce1a245a - e1a239ae3c6bfd9e7871f7e5d8bae86b01a102818201582087428fc52280 - 3d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7a200d902 - 59a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4 - ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820158200263 - 829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f - a200d90259a3016441526f540465302e312e34055820acbb11c7e4da2172 - 05523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a102818201 - 5820a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da - 779a14780281a200d820784068747470733a2f2f706172656e742e657861 - 6d706c652f72696d732f63636233616138352d363162342d343066312d38 - 3438652d3032616436653861323534620182015820e45b72f5c0c0b572db - 4d8d3ab7e97f368ff74e62347a824decb67a84e5224d750382482b060104 - 01a02064781c687474703a2f2f61726d2e636f6d2f696f742f70726f6669 - 6c652f3104a200c11a61ce480001c11a695467800581a3006941434d4520 - 4c74642e01d8206c61636d652e6578616d706c6502810158400f79b7a7de - b0480ed9c5b8b29aaffef33b1094f4e11409dbbbc4c1c2abf79fda30d72e - b86d19053dc5caada1abe76f1e13cad3bef27eebc09086463fe02f5ac4 - `) - testSignedCorimInvalid = comid.MustHexDecode(nil, ` -d284585da3012603746170706c69636174696f6e2f72696d2b63626f7208 -5841a200a2007441434d45204c7464207369676e696e67206b657901d820 -7468747470733a2f2f61636d652e6578616d706c6501a200c11a61ce4800 -01c11a69546780a041a044deadbeef - `) + //go:embed testcases/ec-p256.jwk + testECKey []byte + + //go:embed testcases/signed-corim-valid.cbor + testSignedCorimValid []byte + + //go:embed testcases/signed-corim-invalid.cbor + testSignedCorimInvalid []byte + + //go:embed testcases/signed-corim-valid-with-cots.cbor + testSignedCorimValidWithCots []byte + + //go:embed testcases/psa-refval.cbor + PSARefValCBOR []byte + + //go:embed testcases/test-comid.cbor + testComid []byte - testSignedCorimValidWithCots = comid.MustHexDecode(nil, ` - d2845835a3012603746170706c69636174696f6e2f72696d2b63626f - 72085819a100a1007441434d45204c7464207369676e696e67206b65 - 79a05908aea200505c57e8f446cd421b91c908cf93e13cfc01815908 - 96d901fba20281a101a100a100d86f442a03040506a1008482015902 - d9a28202d5308202d13059301306072a8648ce3d020106082a8648ce - 3d03010703420004cdd1fe64cf2c04cd93986da576dcedcdf1c92edf - d2682cd8e51cfc0409b5c30d6a742e900ed73db8f3f868cba516fd36 - 4c4cf3d1ffddd7c07b06d7a992172f1704148a84cff98095a3bc36d6 - eea518d6978d9bd71f603082025c305c310b300906035504060c0255 - 53311f301d060355040a0c16536e6f6262697368204170706172656c - 2c20496e632e312c302a06035504030c23536e6f6262697368204170 - 706172656c2c20496e632e20547275737420416e63686f72a08201fa - 3082019fa0030201020214101b934465c01045441e1bb8c5a7c09ea9 - bea988300a06082a8648ce3d040302305c310b300906035504060c02 - 5553311f301d060355040a0c16536e6f626269736820417070617265 - 6c2c20496e632e312c302a06035504030c23536e6f62626973682041 - 70706172656c2c20496e632e20547275737420416e63686f72301e17 - 0d3232303531393135313330385a170d333230353136313531333038 - 5a305c310b300906035504060c025553311f301d060355040a0c1653 - 6e6f6262697368204170706172656c2c20496e632e312c302a060355 - 04030c23536e6f6262697368204170706172656c2c20496e632e2054 - 7275737420416e63686f723059301306072a8648ce3d020106082a86 - 48ce3d03010703420004cdd1fe64cf2c04cd93986da576dcedcdf1c9 - 2edfd2682cd8e51cfc0409b5c30d6a742e900ed73db8f3f868cba516 - fd364c4cf3d1ffddd7c07b06d7a992172f17a33f303d301d0603551d - 0e041604148a84cff98095a3bc36d6eea518d6978d9bd71f60300b06 - 03551d0f040403020284300f0603551d130101ff040530030101ff30 - 0a06082a8648ce3d0403020349003046022100b671fe377f73cf9423 - bafddc6fe347ed220c714e828717bd94cc436fefecb8ca02210081ad - 0bfafa48668531a3fbcc3d8496806e2174edc96bbc1f097e60675745 - 8f7782015902baa28202b6308202b23059301306072a8648ce3d0201 - 06082a8648ce3d0301070342000497cf6d70d76a30400c79f1ebab6a - d6168871248710d4f4c1207adaef02d19867136791f2c2b29f6f2dcc - 3c2125c2f2d2fdee4f59094b67af43332f0dfb5ca4400414f6dad1e5 - 128bbf0de9e95343b371c6f7ffe7e26e3082023d3052310b30090603 - 5504060c025553311a3018060355040a0c115a657374792048616e64 - 732c20496e632e3127302506035504030c1e5a657374792048616e64 - 732c20496e632e20547275737420416e63686f72a08201e53082018b - a00302010202140bdc4aa05179503e58f275d552467347bcafb53330 - 0a06082a8648ce3d0403023052310b300906035504060c025553311a - 3018060355040a0c115a657374792048616e64732c20496e632e3127 - 302506035504030c1e5a657374792048616e64732c20496e632e2054 - 7275737420416e63686f72301e170d3232303531393135313330375a - 170d3332303531363135313330375a3052310b300906035504060c02 - 5553311a3018060355040a0c115a657374792048616e64732c20496e - 632e3127302506035504030c1e5a657374792048616e64732c20496e - 632e20547275737420416e63686f723059301306072a8648ce3d0201 - 06082a8648ce3d0301070342000497cf6d70d76a30400c79f1ebab6a - d6168871248710d4f4c1207adaef02d19867136791f2c2b29f6f2dcc - 3c2125c2f2d2fdee4f59094b67af43332f0dfb5ca440a33f303d301d - 0603551d0e04160414f6dad1e5128bbf0de9e95343b371c6f7ffe7e2 - 6e300b0603551d0f040403020284300f0603551d130101ff04053003 - 0101ff300a06082a8648ce3d040302034800304502201da58be7fa44 - 2c6cd849ef3567224a9203c225158966b21afd40f3192cf347980221 - 009827b3e0a1aba2514a3994fa6efa9fd6c610b8905fbed93fcb5250 - 754be8aa58820159027ea282027a308202763059301306072a8648ce - 3d020106082a8648ce3d03010703420004e351aa10392407a4bd037c - a0bc1154d0e701f0674f39cb2c4f9210692cebbeec1d27977dc56165 - 751e0e237bfdfb1536e99a884591429661df35cfc0bf2b50cc041401 - 5c45c9acb0462a715dd710a078c01549f1013f30820201303e310b30 - 0906035504060c0255533110300e060355040a0c074578616d706c65 - 311d301b06035504030c144578616d706c6520547275737420416e63 - 686f72a08201bd30820164a003020102021500d09d90bf3d525cc773 - d522ed77d59e22bba45b88300a06082a8648ce3d040302303e310b30 - 0906035504060c0255533110300e060355040a0c074578616d706c65 - 311d301b06035504030c144578616d706c6520547275737420416e63 - 686f72301e170d3232303531393135313330375a170d333230353136 - 3135313330375a303e310b300906035504060c0255533110300e0603 - 55040a0c074578616d706c65311d301b06035504030c144578616d70 - 6c6520547275737420416e63686f723059301306072a8648ce3d0201 - 06082a8648ce3d03010703420004e351aa10392407a4bd037ca0bc11 - 54d0e701f0674f39cb2c4f9210692cebbeec1d27977dc56165751e0e - 237bfdfb1536e99a884591429661df35cfc0bf2b50cca33f303d301d - 0603551d0e04160414015c45c9acb0462a715dd710a078c01549f101 - 3f300b0603551d0f040403020284300f0603551d130101ff04053003 - 0101ff300a06082a8648ce3d040302034700304402200b06274006c7 - e9cc6d254cbf4487d6fa0146ea9f317e9281196e0be9a6fbedf50220 - 56813e6e110dd23eb048fcde3e32eb11d0fe3c48328c7279adb035d5 - 23eaff538202585b3059301306072a8648ce3d020106082a8648ce3d - 03010703420004ad8a0c01da9eda0253dc2bc27227d9c7213df8df13 - e89cb9cdb7a8e4b62d9ce8a99a2d705c0f7f80db65c006d1091422b4 - 7fc611cbd46869733d9c483884d5fe5840fd190f51a1f11dd477335e - 82b0304e9f5987d43c921cb6ec43e23dda1603ff6f1f3bdab11a5467 - 039d0ac0901cf2d344001c4fcf12629ea116ebf758aae0ec79 - `) - PSARefValCBOR = comid.MustHexDecode(nil, ` -a40065656e2d474201a1005043bbe37f2e614b33aed353cff1428b160281 -a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e65 -78616d706c65028300010204a1008182a100a300d90258582061636d652d -696d706c656d656e746174696f6e2d69642d303030303030303031016441 -434d45026a526f616452756e6e657283a200d90259a30162424c0465322e -312e30055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e -7871f7e5d8bae86b01a102818201582087428fc522803d31065e7bce3cf0 -3fe475096631e5e07bbd7a0fde60c4cf25c7a200d90259a3016450526f54 -0465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae -3c6bfd9e7871f7e5d8bae86b01a10281820158200263829989b6fd954f72 -baaf2fc64bc2e2f01d692d4de72986ea808f6e99813fa200d90259a30164 -41526f540465302e312e34055820acbb11c7e4da217205523ce4ce1a245a -e1a239ae3c6bfd9e7871f7e5d8bae86b01a1028182015820a3a5e715f0cc -574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 - `) - testComid = comid.MustHexDecode(nil, ` -a40065656e2d474201a1005043bbe37f2e614b33aed353cff1428b160281 -a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e65 -78616d706c65028300010204a1008182a100a300d90258582061636d652d -696d706c656d656e746174696f6e2d69642d303030303030303031016441 -434d45026a526f616452756e6e657283a200d90259a30162424c0465322e -312e30055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e -7871f7e5d8bae86b01a102818201582087428fc522803d31065e7bce3cf0 -3fe475096631e5e07bbd7a0fde60c4cf25c7a200d90259a3016450526f54 -0465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae -3c6bfd9e7871f7e5d8bae86b01a10281820158200263829989b6fd954f72 -baaf2fc64bc2e2f01d692d4de72986ea808f6e99813fa200d90259a30164 -41526f540465302e312e34055820acbb11c7e4da217205523ce4ce1a245a -e1a239ae3c6bfd9e7871f7e5d8bae86b01a1028182015820a3a5e715f0cc -574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 - `) - testCoswid = comid.MustHexDecode(nil, ` -a8007820636f6d2e61636d652e727264323031332d63652d7370312d7634 -2d312d352d300c0001783041434d4520526f616472756e6e657220446574 -6563746f72203230313320436f796f74652045646974696f6e205350310d -65342e312e3505a5182b65747269616c182d6432303133182f66636f796f -7465183473526f616472756e6e6572204465746563746f72183663737031 -0282a3181f745468652041434d4520436f72706f726174696f6e18206861 -636d652e636f6d1821820102a3181f75436f796f74652053657276696365 -732c20496e632e18206c6d79636f796f74652e636f6d18210404a2182678 -1c7777772e676e752e6f72672f6c6963656e7365732f67706c2e74787418 -28676c6963656e736506a110a318186a72726465746563746f7218196d25 -70726f6772616d6461746125181aa111a318186e72726465746563746f72 -2e657865141a000820e80782015820a314fc2dc663ae7a6b6bc678759405 -7396e6b3f569cd50fd5ddb4d1bbafd2b6a - `) + //go:embed testcases/test-coswid.cbor + testCoswid []byte - testCots = comid.MustHexDecode(nil, ` - a301a10050ab0f44b1bfdc4604ab4a30f80407ebcc0281a103764d697 - 363656c6c616e656f75732054412053746f726506a1008382005901c1 - 308201bd30820164a003020102021500d09d90bf3d525cc773d522ed7 - 7d59e22bba45b88300a06082a8648ce3d040302303e310b3009060355 - 04060c0255533110300e060355040a0c074578616d706c65311d301b0 - 6035504030c144578616d706c6520547275737420416e63686f72301e - 170d3232303531393135313330375a170d33323035313631353133303 - 75a303e310b300906035504060c0255533110300e060355040a0c0745 - 78616d706c65311d301b06035504030c144578616d706c65205472757 - 37420416e63686f723059301306072a8648ce3d020106082a8648ce3d - 03010703420004e351aa10392407a4bd037ca0bc1154d0e701f0674f3 - 9cb2c4f9210692cebbeec1d27977dc56165751e0e237bfdfb1536e99a - 884591429661df35cfc0bf2b50cca33f303d301d0603551d0e0416041 - 4015c45c9acb0462a715dd710a078c01549f1013f300b0603551d0f04 - 0403020284300f0603551d130101ff040530030101ff300a06082a864 - 8ce3d040302034700304402200b06274006c7e9cc6d254cbf4487d6fa - 0146ea9f317e9281196e0be9a6fbedf5022056813e6e110dd23eb048f - cde3e32eb11d0fe3c48328c7279adb035d523eaff5382015902baa282 - 02b6308202b23059301306072a8648ce3d020106082a8648ce3d03010 - 70342000497cf6d70d76a30400c79f1ebab6ad6168871248710d4f4c1 - 207adaef02d19867136791f2c2b29f6f2dcc3c2125c2f2d2fdee4f590 - 94b67af43332f0dfb5ca4400414f6dad1e5128bbf0de9e95343b371c6 - f7ffe7e26e3082023d3052310b300906035504060c025553311a30180 - 60355040a0c115a657374792048616e64732c20496e632e3127302506 - 035504030c1e5a657374792048616e64732c20496e632e20547275737 - 420416e63686f72a08201e53082018ba00302010202140bdc4aa05179 - 503e58f275d552467347bcafb533300a06082a8648ce3d04030230523 - 10b300906035504060c025553311a3018060355040a0c115a65737479 - 2048616e64732c20496e632e3127302506035504030c1e5a657374792 - 048616e64732c20496e632e20547275737420416e63686f72301e170d - 3232303531393135313330375a170d3332303531363135313330375a3 - 052310b300906035504060c025553311a3018060355040a0c115a6573 - 74792048616e64732c20496e632e3127302506035504030c1e5a65737 - 4792048616e64732c20496e632e20547275737420416e63686f723059 - 301306072a8648ce3d020106082a8648ce3d0301070342000497cf6d7 - 0d76a30400c79f1ebab6ad6168871248710d4f4c1207adaef02d19867 - 136791f2c2b29f6f2dcc3c2125c2f2d2fdee4f59094b67af43332f0df - b5ca440a33f303d301d0603551d0e04160414f6dad1e5128bbf0de9e9 - 5343b371c6f7ffe7e26e300b0603551d0f040403020284300f0603551 - d130101ff040530030101ff300a06082a8648ce3d0403020348003045 - 02201da58be7fa442c6cd849ef3567224a9203c225158966b21afd40f - 3192cf347980221009827b3e0a1aba2514a3994fa6efa9fd6c610b890 - 5fbed93fcb5250754be8aa5882015902d9a28202d5308202d13059301 - 306072a8648ce3d020106082a8648ce3d03010703420004cdd1fe64cf - 2c04cd93986da576dcedcdf1c92edfd2682cd8e51cfc0409b5c30d6a7 - 42e900ed73db8f3f868cba516fd364c4cf3d1ffddd7c07b06d7a99217 - 2f1704148a84cff98095a3bc36d6eea518d6978d9bd71f603082025c3 - 05c310b300906035504060c025553311f301d060355040a0c16536e6f - 6262697368204170706172656c2c20496e632e312c302a06035504030 - c23536e6f6262697368204170706172656c2c20496e632e2054727573 - 7420416e63686f72a08201fa3082019fa0030201020214101b934465c - 01045441e1bb8c5a7c09ea9bea988300a06082a8648ce3d040302305c - 310b300906035504060c025553311f301d060355040a0c16536e6f626 - 2697368204170706172656c2c20496e632e312c302a06035504030c23 - 536e6f6262697368204170706172656c2c20496e632e2054727573742 - 0416e63686f72301e170d3232303531393135313330385a170d333230 - 3531363135313330385a305c310b300906035504060c025553311f301 - d060355040a0c16536e6f6262697368204170706172656c2c20496e63 - 2e312c302a06035504030c23536e6f6262697368204170706172656c2 - c20496e632e20547275737420416e63686f723059301306072a8648ce - 3d020106082a8648ce3d03010703420004cdd1fe64cf2c04cd93986da - 576dcedcdf1c92edfd2682cd8e51cfc0409b5c30d6a742e900ed73db8 - f3f868cba516fd364c4cf3d1ffddd7c07b06d7a992172f17a33f303d3 - 01d0603551d0e041604148a84cff98095a3bc36d6eea518d6978d9bd7 - 1f60300b0603551d0f040403020284300f0603551d130101ff0405300 - 30101ff300a06082a8648ce3d0403020349003046022100b671fe377f - 73cf9423bafddc6fe347ed220c714e828717bd94cc436fefecb8ca022 - 10081ad0bfafa48668531a3fbcc3d8496806e2174edc96bbc1f097e60 - 6757458f77 - `) + //go:embed testcases/test-cots.cbor + testCots []byte ) diff --git a/cocli/cmd/testcases/ec-p256.jwk b/cocli/cmd/testcases/ec-p256.jwk new file mode 100644 index 00000000..e3c07719 --- /dev/null +++ b/cocli/cmd/testcases/ec-p256.jwk @@ -0,0 +1,9 @@ +{ + "kty": "EC", + "crv": "P-256", + "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", + "use": "enc", + "kid": "1" +} diff --git a/cocli/cmd/testcases/psa-refval-orig.cbor b/cocli/cmd/testcases/psa-refval-orig.cbor new file mode 100644 index 0000000000000000000000000000000000000000..acd824043708af039e2074805c5e0e597566ba72 GIT binary patch literal 416 zcmZ3&keZsO>+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<=axr6)lMhR(k)ENR0c(W9n%#oOpWIR` zVhyr+a!yJm>fs{GbvD_5=T#Jbe|lrri)_Y)OpQ&95en^2{YRA=Yz^7ss?XVcuzym@ znP&L(LG|7${(A{W&Z{1Wnw=65lpn&9YN%(dXG((UOvz0%JGcFv>R+^Lz5X%pLytbl zX6pJr*KB*$(4RN6(H>^HBiM8UpxaGIFn#gT=b|6ZgnJbq{<&{=_91_x|5vwFTNJq* MI>~vfe3nQB0Gw#8o&W#< literal 0 HcmV?d00001 diff --git a/cocli/cmd/testcases/psa-refval.cbor b/cocli/cmd/testcases/psa-refval.cbor new file mode 100644 index 0000000000000000000000000000000000000000..928a9ac0c5baba0347d8fce477a646a14a326dd4 GIT binary patch literal 417 zcmZ3&keZsO>+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~CL!l(2q@*Hyu%Bt?&9Awy%+LWadmSwZ=UDM6)qd8tJVH<=p3o^TGZ} zDQBAD(+AaitN8CF967Igd=X3`Q*zVH&TW6E`WNk5uYb(@(4!BsnYzBuHQQb_^ykfN zviQ^g7QOHQVsQt^-STWFJAgw^uw8Ouj0c$_wCL;b7c&B9}uaId7HE U5~+arFvSt9)d1*66C$hv08i|#sQ>@~ literal 0 HcmV?d00001 diff --git a/cocli/cmd/testcases/regen-from-src.sh b/cocli/cmd/testcases/regen-from-src.sh new file mode 100644 index 00000000..c27f6c69 --- /dev/null +++ b/cocli/cmd/testcases/regen-from-src.sh @@ -0,0 +1,40 @@ +#!/usr/bin/bash +# Copyright 2024 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 +set -e + +GEN_TESTCASE=$(go env GOPATH)/bin/gen-testcase + +if [[ ! -f ${GEN_TESTCASE} ]]; then + echo "installing gen-testcase" + + go install github.com/veraison/gen-testcase@v0.0.1 +fi + +testcases=( + psa-refval + test-comid + test-coswid + test-cots +) + +signed_testcases=( + signed-corim-valid + signed-corim-invalid + signed-corim-valid-with-cots +) + +for case in "${testcases[@]}"; do + echo "generating ${case}.cbor" + + ${GEN_TESTCASE} "src/${case}.yaml" -o "${case}.cbor" +done + +for case in "${signed_testcases[@]}"; do + echo "generating ${case}.cbor" + + ${GEN_TESTCASE} -s ec-p256.jwk -m "src/${case}-meta.yaml" "src/${case}.yaml" \ + -o "${case}.cbor" +done + +echo "done." diff --git a/cocli/cmd/testcases/signed-corim-invalid.cbor b/cocli/cmd/testcases/signed-corim-invalid.cbor new file mode 100644 index 0000000000000000000000000000000000000000..06847a2d781fcaec03fb4cdf00661d2875ee1718 GIT binary patch literal 166 zcmV;X09pUigjijp0VV@V>Xa%pWVV`6V|2v|X)0ipoG8ez^z00F@o zX;f!`0HOeNK|@VNAWU>*AaiMFZfS03AZulL0oWjPXmoUNb2=|CVPkD&E@gOOZE$R5 zph2KmKqL`CHB~wi6tc6thb7XDMcY9z@g69{3pbb$2OlO@sTSnDl6=nb&kwY9Qoosi UxH9$v{mg6lF@Iab{#Ch2qTg zyv)3Gh3wSI1(6)<7BK|GgunRWcGgLH;z^G4lONjrVQh@#n0Ax#_adgog^VE0H}YMy zm|0la7BaLnF-9`oT-3yL)u4&#qCupAFdMs8o5wj@CPp?6FoT(qo!NfOJTp>yM@%pVrct%rHDO7tf1UANuw`A5d-rDA{0e0)A%{D1fQ zfoitvD<_HTi?fJywVeOiFm>@Bvup2`N?e=XJNvqP0?^VJgBU|@15P&PP!={GrqEzR zc>`Gxhl@unI4?gbDYH03!Lgtqu_!f1N5L~MS#G(&7>Y$Gqf> z{GtU-jK2(;80RlwW@2Pw5)qJ|?2>vwz|}=gddJb_2j;EZx3a^43+h!CW+vJ?#z0P- z*T~4g)X>t<)X><#B1)Xs7{oP$a%pTk?P7Pay@4$_+GP1y#BfA4H!#v<`B{L0%+zAQ z58?Yjc+x8Qn~fdfzF$JeW~u8DONsehRvi!;)N(fX=$@@25|=E7s&|380}2ej1IAP6;eOBqN& zQnz4KYH>-Wf=6OrN-;EHs~f0761E(M9D1653QW`8&@|0`$7?}gWq@78r_!rIZpH3< z)^9aN4o_M*0WBq)qo!o@D4N)bJN;4`e`x6!Yh)6iemxirT$vPPmv%q@<)V{w!}Gmq zx{}u<=0mEYooSn-{yKb?)cNc_1DJegsBeC-aP^`A$tRwU*YEJ*n;mCe;*3QWa&mFn7X! z+n|`^#aETymS3Hxw0lW3YWYi{eFW1brRL*KgOo-BS{lR}BgCgccLoC&CIxObbqBWN zFVEzv`s{aUzxIpK?bUq4x=D?adE76T{eJtENg=G!E>Dp6lHCT6Klkj6UJG9MXX9bi zQ&hQjgXvY}SO0^Xm?EO_q`b9VJdC&I-C_#9qkX7I{pN8++aLFZU(DHgcKeDa+jQr= zSUF3#Acnub;dbf)wu_u1N?YoW37)=^ky&gz$HSuK>c0pFSO5M{;ka)Hb}wWwp5L&; zF<|Sd75S%A6PV7fp1n}*xe?n>=dx3ipH(`FE(o%1jeK{->HcHcAnOY?`~S>${;~Ek E0MOJRdjJ3c literal 0 HcmV?d00001 diff --git a/cocli/cmd/testcases/signed-corim-valid.cbor b/cocli/cmd/testcases/signed-corim-valid.cbor new file mode 100644 index 0000000000000000000000000000000000000000..1a9d42ed461ac3cef3491d22b47220e78ee73c3c GIT binary patch literal 799 zcmccA5)r$YQH{AIv7jI)GdZy&Ge1wiC^J_(IVr!0Bf@bJ<08g`Qkfy?4GagR63=-s zFf3vyadh@|Rq!cEQ7Fz#&&$k9SIADSWW1qJl2KApP;8~IpO~DRs+U@km|KvOx*(FN zVi^mXA*_vyf$DRR)iE_PGGxLG)?)~W34ig$?W~jZ#FHH7CqK0L!`K+fxcDaHuO*BN z83LSlKd#qH^fq31Ir#iXr*1K(#>GgQaXE#lnSqgsWg$aj(?W*DOj$wsi77#)d3mWt z3^$n~DilDj(ai+8IX5-01RN5&nJKykP+-WI0&-sSBB1KX#f(W#J}jw5dWL!itPu)p zb_*VVa!av@HOS`4IVqK>hl?!N*<}BnS5f%=>5W}4vKbdLH8wFuD6~8EA606wHDrsc zK4?qg3I|w%DM%~=201LrfYM*FesXe>ablu{sjit}l8LT~ zL7Jhig^5L~u7Ocvidm{fqLHad5_5$N$Our9E6UYN&d=4)%rDU|D9TUE%t_TZjBp6p zQkipB*nL;=LZf}fPk&tL@GWn@_dM^6GtcZht^64o8mt}6b9!52|L*U0$S=*=X?O1f S+n>HkCjATVUYy5UxEBD=Sw&_5 literal 0 HcmV?d00001 diff --git a/cocli/cmd/testcases/src/psa-refval.yaml b/cocli/cmd/testcases/src/psa-refval.yaml new file mode 100644 index 00000000..0fae32a4 --- /dev/null +++ b/cocli/cmd/testcases/src/psa-refval.yaml @@ -0,0 +1,60 @@ +--- +0: en-GB +1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== +2: +- 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 +4: + 0: + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK + # acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/cocli/cmd/testcases/src/signed-corim-invalid-meta.yaml b/cocli/cmd/testcases/src/signed-corim-invalid-meta.yaml new file mode 100644 index 00000000..f3be19ff --- /dev/null +++ b/cocli/cmd/testcases/src/signed-corim-invalid-meta.yaml @@ -0,0 +1,13 @@ +--- +0: + 0: ACME Ltd signing key + 1: + tag: 32 + value: https://acme.example +1: + 0: + tag: 1 + value: 1640908800 + 1: + tag: 1 + value: 1767139200 diff --git a/cocli/cmd/testcases/src/signed-corim-invalid.yaml b/cocli/cmd/testcases/src/signed-corim-invalid.yaml new file mode 100644 index 00000000..2fbf0ffd --- /dev/null +++ b/cocli/cmd/testcases/src/signed-corim-invalid.yaml @@ -0,0 +1 @@ +--- {} diff --git a/cocli/cmd/testcases/src/signed-corim-valid-meta.yaml b/cocli/cmd/testcases/src/signed-corim-valid-meta.yaml new file mode 100644 index 00000000..abffebe3 --- /dev/null +++ b/cocli/cmd/testcases/src/signed-corim-valid-meta.yaml @@ -0,0 +1,13 @@ +--- +1: + 0: + tag: 1 + value: 1640908800 + 1: + tag: 1 + value: 1767139200 +0: + 1: + tag: 32 + value: https://acme.example + 0: ACME Ltd signing key diff --git a/cocli/cmd/testcases/src/signed-corim-valid-with-cots-meta.yaml b/cocli/cmd/testcases/src/signed-corim-valid-with-cots-meta.yaml new file mode 100644 index 00000000..e472e598 --- /dev/null +++ b/cocli/cmd/testcases/src/signed-corim-valid-with-cots-meta.yaml @@ -0,0 +1,3 @@ +--- +0: + 0: ACME Ltd signing key diff --git a/cocli/cmd/testcases/src/signed-corim-valid-with-cots.yaml b/cocli/cmd/testcases/src/signed-corim-valid-with-cots.yaml new file mode 100644 index 00000000..de094026 --- /dev/null +++ b/cocli/cmd/testcases/src/signed-corim-valid-with-cots.yaml @@ -0,0 +1,6 @@ +--- +0: !!binary |- + XFfo9EbNQhuRyQjPk+E8/A== +1: +- !!binary |- + 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 diff --git a/cocli/cmd/testcases/src/signed-corim-valid.yaml b/cocli/cmd/testcases/src/signed-corim-valid.yaml new file mode 100644 index 00000000..1a8c4a67 --- /dev/null +++ b/cocli/cmd/testcases/src/signed-corim-valid.yaml @@ -0,0 +1,87 @@ +0: !!binary |- + XFfo9EbNQhuRyQjPk+E8/A== +1: +- encodedCBOR: + tag: 506 + value: + 0: en-GB + 1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== + 2: + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + 4: + 0: + - - 0: + 0: + tag: 600 + value: acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= +2: +- 0: + tag: 32 + value: https://parent.example/rims/ccb3aa85-61b4-40f1-848e-02ad6e8a254b + 1: + - 1 + - !!binary |- + 5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU= +3: http://arm.com/iot/profile/1 +4: + 0: + tag: 1 + value: 1640908800 + 1: + tag: 1 + value: 1767139200 +5: +- 0: ACME Ltd. + 1: + tag: 32 + value: acme.example + 2: + - 1 + diff --git a/cocli/cmd/testcases/src/test-comid.yaml b/cocli/cmd/testcases/src/test-comid.yaml new file mode 100644 index 00000000..0fae32a4 --- /dev/null +++ b/cocli/cmd/testcases/src/test-comid.yaml @@ -0,0 +1,60 @@ +--- +0: en-GB +1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== +2: +- 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 +4: + 0: + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK + # acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/cocli/cmd/testcases/src/test-coswid.yaml b/cocli/cmd/testcases/src/test-coswid.yaml new file mode 100644 index 00000000..912c1a67 --- /dev/null +++ b/cocli/cmd/testcases/src/test-coswid.yaml @@ -0,0 +1,35 @@ +--- +0: com.acme.rrd2013-ce-sp1-v4-1-5-0 +12: 0 +1: ACME Roadrunner Detector 2013 Coyote Edition SP1 +13: 4.1.5 +5: + 43: trial + 45: '2013' + 47: coyote + 52: Roadrunner Detector + 54: sp1 +2: +- 31: The ACME Corporation + 32: acme.com + 33: + - 1 + - 2 +- 31: Coyote Services, Inc. + 32: mycoyote.com + 33: 4 +4: + 38: www.gnu.org/licenses/gpl.txt + 40: license +6: + 16: + 24: rrdetector + 25: "%programdata%" + 26: + 17: + 24: rrdetector.exe + 20: 532712 + 7: + - 1 + - !!binary |- + oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2o= diff --git a/cocli/cmd/testcases/src/test-cots.yaml b/cocli/cmd/testcases/src/test-cots.yaml new file mode 100644 index 00000000..af6af0c2 --- /dev/null +++ b/cocli/cmd/testcases/src/test-cots.yaml @@ -0,0 +1,17 @@ +--- +1: + 0: !!binary |- + qw9Esb/cRgSrSjD4BAfrzA== +2: +- 3: Miscellaneous TA Store +6: + 0: + - - 0 # pkix-cert-type + - !!binary |- + MIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M= + - - 1 # pkix-tainfo-type + - !!binary |- + ooICtjCCArIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXz21w12owQAx58euratYWiHEkhxDU9MEgetrvAtGYZxNnkfLCsp9vLcw8ISXC8tL97k9ZCUtnr0MzLw37XKRABBT22tHlEou/DenpU0Ozccb3/+fibjCCAj0wUjELMAkGA1UEBgwCVVMxGjAYBgNVBAoMEVplc3R5IEhhbmRzLCBJbmMuMScwJQYDVQQDDB5aZXN0eSBIYW5kcywgSW5jLiBUcnVzdCBBbmNob3KgggHlMIIBi6ADAgECAhQL3EqgUXlQPljyddVSRnNHvK+1MzAKBggqhkjOPQQDAjBSMQswCQYDVQQGDAJVUzEaMBgGA1UECgwRWmVzdHkgSGFuZHMsIEluYy4xJzAlBgNVBAMMHlplc3R5IEhhbmRzLCBJbmMuIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMFIxCzAJBgNVBAYMAlVTMRowGAYDVQQKDBFaZXN0eSBIYW5kcywgSW5jLjEnMCUGA1UEAwweWmVzdHkgSGFuZHMsIEluYy4gVHJ1c3QgQW5jaG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl89tcNdqMEAMefHrq2rWFohxJIcQ1PTBIHra7wLRmGcTZ5HywrKfby3MPCElwvLS/e5PWQlLZ69DMy8N+1ykQKM/MD0wHQYDVR0OBBYEFPba0eUSi78N6elTQ7Nxxvf/5+JuMAsGA1UdDwQEAwIChDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIB2li+f6RCxs2EnvNWciSpIDwiUViWayGv1A8xks80eYAiEAmCez4KGrolFKOZT6bvqf1sYQuJBfvtk/y1JQdUvoqlg= + - - 1 # pkix-tainfo-type + - !!binary |- + ooIC1TCCAtEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN0f5kzywEzZOYbaV23O3N8cku39JoLNjlHPwECbXDDWp0LpAO1z248/hoy6UW/TZMTPPR/93XwHsG16mSFy8XBBSKhM/5gJWjvDbW7qUY1peNm9cfYDCCAlwwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yoIIB+jCCAZ+gAwIBAgIUEBuTRGXAEEVEHhu4xafAnqm+qYgwCgYIKoZIzj0EAwIwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMB4XDTIyMDUxOTE1MTMwOFoXDTMyMDUxNjE1MTMwOFowXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzdH+ZM8sBM2TmG2ldtztzfHJLt/SaCzY5Rz8BAm1ww1qdC6QDtc9uPP4aMulFv02TEzz0f/d18B7BtepkhcvF6M/MD0wHQYDVR0OBBYEFIqEz/mAlaO8NtbupRjWl42b1x9gMAsGA1UdDwQEAwIChDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQC2cf43f3PPlCO6/dxv40ftIgxxToKHF72UzENv7+y4ygIhAIGtC/r6SGaFMaP7zD2EloBuIXTtyWu8Hwl+YGdXRY93 diff --git a/cocli/cmd/testcases/test-comid.cbor b/cocli/cmd/testcases/test-comid.cbor new file mode 100644 index 0000000000000000000000000000000000000000..ddb69dcc2cfb54984edf332a5aa81e9df1e62c44 GIT binary patch literal 417 zcmZ3&vXG&%X(7X6#uP_qUstBAp!~#?pwhg&)FOtPOc4=^iOIRCx|z8JIjOm+c_oP@ znfZCTnJKykP+-W_yohljQ)3fjghIPh|52p|TSKT@<9?4Oi!rWrncP`$T`|6an8 z^Qy;z21YJsOmgyJNj1_l)H7g>P*}5D@c5HkibbqJHc!q;sYE?oWVy~J`|rGp!tYOS z?0S*C2&{)C)lkn^&y)!Dj41&@`5`a|F(o(6?A-Qus(;b0_4>!W4?X%Io2l#jT(j*} zLx0}PMtiu+7cYG-`r%BtSMlMW`*vp^@;CZ_bz8MXk;|czoVUtniBv#bp5h2JnEB_##LR{Hv&FxN}1NCbsC06_k&sQ>@~ literal 0 HcmV?d00001 diff --git a/cocli/cmd/testcases/test-coswid.cbor b/cocli/cmd/testcases/test-coswid.cbor new file mode 100644 index 0000000000000000000000000000000000000000..070640ef47ef560f988b6b3e60fd2533ab8e3457 GIT binary patch literal 377 zcmZvYy-or_6or9+T4b<(Mu|YlcGkmd|0R2LIH-DGz)jGm2qPUv-eoXX{e= zQkk=AARUu?$hh=if0WK7T(}%>qv|^xgClYwI0NE(!^P49&SoFs&~_6-1PBMw?qD!r zwg{P2c6oUY%C_$^9qEurZ$UE7;nY-LTZhCt&Y;yB+mcP z34gik^(Ikxp9>y7U%#FA*7WA1x$`zHbpga*8N&!u5lI}_Tqa3oub~`iB2W8OYS0?3 HtfzhfW&VMf literal 0 HcmV?d00001 diff --git a/cocli/cmd/testcases/test-cots.cbor b/cocli/cmd/testcases/test-cots.cbor new file mode 100644 index 0000000000000000000000000000000000000000..17470aca611c1c94344b496da23094ee5fe9b675 GIT binary patch literal 1943 zcmZ3?)VPqj%r~<*IW;FIF)ua0v{)g;Q6ab_zbKV$AwzQ$LnPxtgC@qkK%BCGnTe5! zNtEHj+zI<_gJO;sUsZZres!MG?j_M323%|$T5TTZY+0C@4D1ZK4LI4DLs{5(m_maM z1q}E=94;Pq*NVj4f}B)CSp#X12s4ieR74@9sI<65!7(p6BfrQ%PMp`s$iURl($Lh< z*uXqWoYxq{HG^^~Hb2rpn2jCmS|&!QvzZy$nVlF|9tW-xuvB4RvX{AL!5+bo3(pxp zr2AW**72Vtkg4-}-y2!=>9t1_Q%mLel&k;#7Bzc0tHX7o)3n6x}UKdzUaX{4bBcr_mH%OYF1sG6FEe8A`jxZzRe->5) zW*~(ej?C^11};nr+-&L&Y{y@o$yN2)@6vwl7o*#&`G$3q8YT0%UoQLo_A8S@SfgE@ zAnzr+4IY2)*%`eSyztM)!>FgIa_t7wtIDtb2RAWBGVNN_#I((ziD{DoZr@KopIdM} z%fNxB^5g5(S=Yom3RT(#u6#MDP<87))5RI-!s!z~9ojTMU-yiSqUxbfm;S!E*B%}-^KnZ}0M=&b2 zxTI3SBQYqZurk0d;#29> zAh%-oJ?pm`BZnp}oIo(8(!kp^j6by0i#0NdPrV)t2ChsBvP-+4|8mjEx#9WVG+oJS z67wNd(ay9@QhyyjOX_@fpTVTaFhhOwgN3UX1$tRd`IYx;{$qD4^8) z#i|HU^1TU6zE^=M=^`HQpS}1m<-88d*~v3$W>S|HwGKRP3*rkI(0e|L8^YW9DGK(`59198(i&Ar-X;{ZV3zCMFu`57N$iILI zc|J5D3rJ6PNj)Io>LMq-oPqF0f$?QGyoOKE5;jaDVj0+h8R`a`T+<(W7Wwn>V4;J> Date: Fri, 12 Jul 2024 13:18:27 +0100 Subject: [PATCH 045/110] feat: implement CoRIM profiles - Add RegisterProfile and UnregisterProfile to associate an extensions.Map with an eat.Profile inside a new Profile structure. - Profile is able to instantiate new signed and unsigned CoRIMs, and CoMIDs with appropriate extensions registered to them. - Add unmarshaling functions that attempt to identify the eat.Profile within the raw data, and retrieve an appropriate Profile, which is then used to get an instance with appropriate extensions registered, which can then be unmarshaled fully. Signed-off-by: Sergei Trofimov --- corim/common_test.go | 115 ++++++ corim/example_profile_test.go | 199 +++++++++++ corim/profiles.go | 326 ++++++++++++++++++ corim/profiles_test.go | 187 ++++++++++ corim/signedcorim.go | 5 + corim/signedcorim_test.go | 4 +- corim/testcases/comid-ext.json | 51 +++ corim/testcases/comid.json | 49 +++ corim/testcases/corim-ext.json | 18 + corim/testcases/corim.json | 16 + corim/testcases/regen-from-src.sh | 30 ++ .../signed-corim-with-extensions.cbor | Bin 0 -> 681 bytes corim/testcases/signed-example-corim.cbor | Bin 0 -> 684 bytes corim/testcases/signed-good-corim.cbor | Bin 0 -> 607 bytes .../testcases/src/corim-with-extensions.yaml | 75 ++++ corim/testcases/src/ec-p256.jwk | 9 + corim/testcases/src/example-corim.yaml | 78 +++++ corim/testcases/src/good-corim.yaml | 66 ++++ corim/testcases/src/meta.yaml | 13 + .../unsigned-corim-with-extensions.cbor | Bin 0 -> 514 bytes corim/testcases/unsigned-example-corim.cbor | Bin 0 -> 517 bytes corim/testcases/unsigned-good-corim.cbor | Bin 0 -> 440 bytes corim/unsignedcorim_test.go | 11 +- extensions/README.md | 70 +++- 24 files changed, 1311 insertions(+), 11 deletions(-) create mode 100644 corim/common_test.go create mode 100644 corim/example_profile_test.go create mode 100644 corim/profiles.go create mode 100644 corim/profiles_test.go create mode 100644 corim/testcases/comid-ext.json create mode 100644 corim/testcases/comid.json create mode 100644 corim/testcases/corim-ext.json create mode 100644 corim/testcases/corim.json create mode 100644 corim/testcases/regen-from-src.sh create mode 100644 corim/testcases/signed-corim-with-extensions.cbor create mode 100644 corim/testcases/signed-example-corim.cbor create mode 100644 corim/testcases/signed-good-corim.cbor create mode 100644 corim/testcases/src/corim-with-extensions.yaml create mode 100644 corim/testcases/src/ec-p256.jwk create mode 100644 corim/testcases/src/example-corim.yaml create mode 100644 corim/testcases/src/good-corim.yaml create mode 100644 corim/testcases/src/meta.yaml create mode 100644 corim/testcases/unsigned-corim-with-extensions.cbor create mode 100644 corim/testcases/unsigned-example-corim.cbor create mode 100644 corim/testcases/unsigned-good-corim.cbor diff --git a/corim/common_test.go b/corim/common_test.go new file mode 100644 index 00000000..12ebe10d --- /dev/null +++ b/corim/common_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package corim + +import ( + _ "embed" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + // minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate + //go:embed testcases/unsigned-good-corim.cbor + testGoodUnsignedCorimCBOR []byte + + // comid entity and unsigned-corim are extended + //go:embed testcases/unsigned-corim-with-extensions.cbor + testUnsignedCorimWithExtensionsCBOR []byte + + // comid entity and unsigned-corim are extended + //go:embed testcases/signed-good-corim.cbor + testGoodSignedCorimCBOR []byte + + // comid entity and unsigned-corim are extended + //go:embed testcases/signed-corim-with-extensions.cbor + testSignedCorimWithExtensionsCBOR []byte + + //go:embed testcases/corim.json + testUnsignedCorimJSON []byte + + //go:embed testcases/corim-ext.json + testUnsignedCorimWithExtensionsJSON []byte + + //go:embed testcases/comid.json + testComidJSON []byte + + //go:embed testcases/comid-ext.json + testComidWithExtensionsJSON []byte +) + +func assertCoRIMEq(t *testing.T, expected []byte, actual []byte, msgAndArgs ...interface{}) bool { + var expectedCoRIM, actualCoRIM *UnsignedCorim + + if err := dm.Unmarshal(expected, &expectedCoRIM); err != nil { + return assert.Fail(t, fmt.Sprintf( + "Expected value ('%s') is not valid UnsignedCorim: '%s'", + expected, err.Error()), msgAndArgs...) + } + + if err := dm.Unmarshal(actual, &actualCoRIM); err != nil { + return assert.Fail(t, fmt.Sprintf( + "actual value ('%s') is not valid UnsignedCorim: '%s'", + actual, err.Error()), msgAndArgs...) + } + + if !assert.EqualValues(t, expectedCoRIM.ID, actualCoRIM.ID, msgAndArgs...) { + return false + } + + if !assert.EqualValues(t, expectedCoRIM.DependentRims, + actualCoRIM.DependentRims, msgAndArgs...) { + return false + } + + if !assert.EqualValues(t, expectedCoRIM.Profile, actualCoRIM.Profile, msgAndArgs...) { + return false + } + + if !assert.EqualValues(t, expectedCoRIM.RimValidity, + actualCoRIM.RimValidity, msgAndArgs...) { + return false + } + + if !assert.EqualValues(t, expectedCoRIM.Entities, actualCoRIM.Entities, msgAndArgs...) { + return false + } + + if len(expectedCoRIM.Tags) != len(actualCoRIM.Tags) { + allMsgAndArgs := []interface{}{len(expectedCoRIM.Tags), len(actualCoRIM.Tags)} + allMsgAndArgs = append(allMsgAndArgs, msgAndArgs...) + return assert.Fail(t, fmt.Sprintf( + "Unexpected number of Tags: expected %d, actual %d", allMsgAndArgs...)) + } + + for i, expectedTag := range expectedCoRIM.Tags { + actualTag := actualCoRIM.Tags[i] + + if !assertCBOREq(t, expectedTag, actualTag, msgAndArgs...) { + return false + } + } + + return true +} + +func assertCBOREq(t *testing.T, expected []byte, actual []byte, msgAndArgs ...interface{}) bool { + var expectedCBOR, actualCBOR interface{} + + if err := dm.Unmarshal(expected, &expectedCBOR); err != nil { + return assert.Fail(t, fmt.Sprintf( + "Expected value ('%s') is not valid cbor.\nCBOR parsing error: '%s'", + expected, err.Error()), msgAndArgs...) + } + + if err := dm.Unmarshal(actual, &actualCBOR); err != nil { + return assert.Fail(t, fmt.Sprintf( + "Input ('%s') needs to be valid cbor.\nCBOR parsing error: '%s'", + actual, err.Error()), msgAndArgs...) + } + + return assert.Equal(t, expectedCBOR, actualCBOR, msgAndArgs...) +} diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go new file mode 100644 index 00000000..56e388f7 --- /dev/null +++ b/corim/example_profile_test.go @@ -0,0 +1,199 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "encoding/hex" + "errors" + "fmt" + "log" + "os" + "time" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" + "github.com/veraison/swid" +) + +// ----- profile definition ----- +// The following code defines a profile with the following extensions and +// constraints: +// - Entities may contain an address field +// - Reference values may contain a Unix timestamp indicating when the +// individual measurement was taken. +// - The language claim (CoMID index 0) must be present, and must be "en-GB" + +// EntityExtensions will be used for both CoMID and CoRIM entities. +type EntityExtensions struct { + Address *string `cbor:"-1,keyasint,omitempty" json:"address,omitempty"` +} + +type RefValExtensions struct { + Timestamp *int `cbor:"-1,keyasint,omitempty" json:"timestamp,omitempty"` +} + +// We're not defining any additional fields, however we're providing extra +// constraints that will be applied on top of standard CoMID validation. +type ComidExtensions struct{} + +func (*ComidExtensions) ConstrainComid(c *comid.Comid) error { + if c.Language == nil { + return errors.New("language not specified") + } + + if *c.Language != "en-GB" { + return fmt.Errorf(`language must be "en-GB", but found %q`, *c.Language) + } + + return nil +} + +// Registering the profile inside init() in the same file where it is defined +// ensures that the profile will always be available, and you don't need to +// remember to register it at the time you want to use it. The only potential +// danger with that is if the your profile ID clashes with another profile, +// which should not happen if it a registered PEN or a URL containing a domain +// that you own. +func init() { + profileID, err := eat.NewProfile("http://example.com/example-profile") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + + extMap := extensions.NewMap(). + Add(ExtEntity, &EntityExtensions{}). + Add(comid.ExtComid, &ComidExtensions{}). + Add(comid.ExtEntity, &EntityExtensions{}). + Add(comid.ExtReferenceValue, &RefValExtensions{}) + + if err := RegisterProfile(profileID, extMap); err != nil { + // will not error, assuming our profile ID is unique, and we've + // correctly set up the extensions Map above + panic(err) + } +} + +// ----- end of profile definition ----- +// The following code demonstrates how the profile might be used. + +func Example_profile_unmarshal() { + buf, err := os.ReadFile("testcases/unsigned-example-corim.cbor") + if err != nil { + log.Fatalf("could not read corim file: %v", err) + } + + // UnmarshalUnsignedCorimFromCBOR will detect the profile and ensure + // the correct extensions are loaded before unmarshalling + extractedCorim, err := UnmarshalUnsignedCorimFromCBOR(buf) + if err != nil { + log.Fatalf("could not unmarshal corim: %v", err) + } + + extractedComid, err := UnmarshalComidFromCBOR( + extractedCorim.Tags[0], + extractedCorim.Profile, + ) + if err != nil { + log.Fatalf("could not unmarshal corim: %v", err) + } + + fmt.Printf("Language: %s\n", *extractedComid.Language) + fmt.Printf("Entity: %s\n", *extractedComid.Entities.Values[0].EntityName) + fmt.Printf(" %s\n", extractedComid.Entities.Values[0]. + Extensions.MustGetString("Address")) + + fmt.Printf("Measurements:\n") + for _, m := range extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values { + + val := hex.EncodeToString((*m.Val.Digests)[0].HashValue) + tsInt := m.Val.Extensions.MustGetInt64("timestamp") + ts := time.Unix(tsInt, 0).UTC() + + fmt.Printf(" %v taken at %s\n", val, ts.Format("2006-01-02T15:04:05")) + } + + // output: + // Language: en-GB + // Entity: ACME Ltd. + // 123 Fake Street + // Measurements: + // 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 taken at 2024-07-12T11:03:10 + // 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f taken at 2024-07-12T11:03:10 + // a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 taken at 2024-07-12T11:03:10 +} + +// note: this example is rather verbose as we're going to be constructing a +// CoMID by hand. In practice, you would typically write a JSON document and +// then unmarshal that into a CoRIM before marshaling it into CBOR (in which +// case, extensions will work as with unmarshaling example above). +func Example_profile_marshal() { + profileID, err := eat.NewProfile("http://example.com/example-profile") + if err != nil { + panic(err) + } + + profile, ok := GetProfile(profileID) + if !ok { + log.Fatalf("profile %v not found", profileID) + } + + myCorim := profile.GetUnsignedCorim() + myComid := profile.GetComid(). + SetLanguage("en-GB"). + SetTagIdentity("example", 0). + // Adding an entity to the Entities collection also registers + // profile's extensions + AddEntity("ACME Ltd.", &comid.TestRegID, comid.RoleCreator) + + address := "123 Fake Street" + err = myComid.Entities.Values[0].Extensions.Set("Address", &address) + if err != nil { + log.Fatalf("could not set entity Address: %v", err) + } + + refVal := comid.ValueTriple{ + Environment: comid.Environment{ + Class: comid.NewClassImplID(comid.TestImplID). + SetVendor("ACME Ltd."). + SetModel("RoadRunner 2.0"), + }, + Measurements: *comid.NewMeasurements(), + } + + measurement := comid.MustNewPSAMeasurement( + comid.MustCreatePSARefValID( + comid.TestSignerID, "BL", "5.0.5", + )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}) + + // alternatively, we can add extensions to individual value before + // adding it to the collection. Note that because we're adding the + // extension directly to the measurement, we're using a different + // extension point, comid.ExtMval rather than comid.ExtReferenceValue, + // as a measurement doesn't know that its going to be part of reference + // value, and so is unaware of reference value extension points. + extMap := extensions.NewMap().Add(comid.ExtMval, &RefValExtensions{}) + if err = measurement.Val.RegisterExtensions(extMap); err != nil { + log.Fatal("could not register refval extensions") + } + + refVal.Measurements.Add(measurement) + myComid.Triples.AddReferenceValue(refVal) + + err = myComid.Valid() + if err != nil { + log.Fatalf("comid validity: %v", err) + } + + myCorim.AddComid(*myComid) + + buf, err := myCorim.ToCBOR() + if err != nil { + log.Fatalf("could not encode CoRIM: %v", err) + } + + fmt.Printf("corim: %v", hex.EncodeToString(buf)) + + // output: + // corim: a300f6018158d9d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 +} diff --git a/corim/profiles.go b/corim/profiles.go new file mode 100644 index 00000000..a5b6ed23 --- /dev/null +++ b/corim/profiles.go @@ -0,0 +1,326 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" + "github.com/veraison/go-cose" +) + +// SignedCorimMapExtensionPoints is a list of extension.Point's valid for a +// SignedCorim. +var SignedCorimMapExtensionPoints = []extensions.Point{ + ExtSigner, + ExtUnsignedCorim, + ExtEntity, +} + +// UnsignedCorimMapExtensionPoints is a list of extension.Point's valid for a +// UnsignedCorim. +var UnsignedCorimMapExtensionPoints = []extensions.Point{ + ExtUnsignedCorim, + ExtEntity, +} + +// ComidMapExtensionPoints is a list of extension.Point's valid for a comid.Comid. +var ComidMapExtensionPoints = []extensions.Point{ + comid.ExtComid, + comid.ExtEntity, + comid.ExtTriples, + comid.ExtReferenceValue, + comid.ExtReferenceValueFlags, + comid.ExtEndorsedValue, + comid.ExtEndorsedValueFlags, +} + +// AllExtensionPoints is a list of all valid extension.Point's +var AllExtensionPoints = make(map[extensions.Point]bool) // populated inside init() below + +// UnmarshalSignedCorimFromCBOR unmarshals a SignedCorim from provided +// CBOR data. If there are extensions associated with the profile specified by +// the data, they will be registered with the UnsignedCorim before it is +// unmarshaled. +func UnmarshalSignedCorimFromCBOR(buf []byte) (*SignedCorim, error) { + message := cose.NewSign1Message() + + if err := message.UnmarshalCBOR(buf); err != nil { + return nil, fmt.Errorf("failed CBOR decoding for COSE-Sign1 signed CoRIM: %w", err) + } + + profiled := struct { + Profile *eat.Profile `cbor:"3,keyasint,omitempty"` + }{} + + if err := dm.Unmarshal(message.Payload, &profiled); err != nil { + return nil, err + } + + ret := GetSignedCorim(profiled.Profile) + if err := ret.FromCOSE(buf); err != nil { + return nil, err + } + + return ret, nil +} + +// UnmarshalUnsignedCorimFromCBOR unmarshals an UnsignedCorim from provided +// CBOR data. If there are extensions associated with the profile specified by +// the data, they will be registered with the UnsignedCorim before it is +// unmarshaled. +func UnmarshalUnsignedCorimFromCBOR(buf []byte) (*UnsignedCorim, error) { + profiled := struct { + Profile *eat.Profile `cbor:"3,keyasint,omitempty"` + }{} + + if err := dm.Unmarshal(buf, &profiled); err != nil { + return nil, err + } + + ret := GetUnsignedCorim(profiled.Profile) + if err := ret.FromCBOR(buf); err != nil { + return nil, err + } + + return ret, nil +} + +// UnmarshalUnsignedCorimFromJSON unmarshals an UnsignedCorim from provided +// JSON data. If there are extensions associated with the profile specified by +// the data, they will be registered with the UnsignedCorim before it is +// unmarshaled. +func UnmarshalUnsignedCorimFromJSON(buf []byte) (*UnsignedCorim, error) { + profiled := struct { + Profile *eat.Profile `json:"profile,omitempty"` + }{} + + if err := json.Unmarshal(buf, &profiled); err != nil { + return nil, err + } + + ret := GetUnsignedCorim(profiled.Profile) + if err := ret.FromJSON(buf); err != nil { + return nil, err + } + + return ret, nil +} + +// UnmarshalComidFromCBOR unmarshals a comid.Comid from provided CBOR data. If +// there are extensions associated with the profile specified by the data, they +// will be registered with the comid.Comid before it is unmarshaled. +func UnmarshalComidFromCBOR(buf []byte, profileID *eat.Profile) (*comid.Comid, error) { + var ret *comid.Comid + + profile, ok := GetProfile(profileID) + if ok { + ret = profile.GetComid() + } else { + ret = comid.NewComid() + } + + if err := ret.FromCBOR(buf); err != nil { + return nil, err + } + + return ret, nil +} + +// GetSingedCorim returns a pointer to a new SingedCorim instance. If there +// are extensions associated with the provided profileID, they will be +// registered with the instance. +func GetSignedCorim(profileID *eat.Profile) *SignedCorim { + var ret *SignedCorim + + if profileID == nil { + ret = NewSignedCorim() + } else { + profile, ok := GetProfile(profileID) + if !ok { + // unknown profile -- treat here like an unprofiled + // CoRIM. While the CoRIM spec states that unknown + // profiles should be rejected, we're not actually + // validating the profile here, just trying to identify + // any extensions we may need to load. Profile + // validation is left up to the calling code, as a + // profile only needs to be registered here if it + // defines extensions. Profiles that do not add any + // additional fields may not be registered. + ret = NewSignedCorim() + } else { + ret = profile.GetSignedCorim() + } + } + + return ret +} + +// GetUnsignedCorim returns a pointer to a new UnsignedCorim instance. If there +// are extensions associated with the provided profileID, they will be +// registered with the instance. +func GetUnsignedCorim(profileID *eat.Profile) *UnsignedCorim { + var ret *UnsignedCorim + + if profileID == nil { + ret = NewUnsignedCorim() + } else { + profile, ok := GetProfile(profileID) + if !ok { + // unknown profile -- treat here like an unprofiled + // CoRIM. While the CoRIM spec states that unknown + // profiles should be rejected, we're not actually + // validating the profile here, just trying to identify + // any extensions we may need to load. Profile + // validation is left up to the calling code, as a + // profile only needs to be registered here if it + // defines extensions. Profiles that do not add any + // additional fields may not be registered. + ret = NewUnsignedCorim() + } else { + ret = profile.GetUnsignedCorim() + } + } + + return ret +} + +// Profile associates an EAT profile ID with a set of extensions. It allows +// obtaining new CoRIM and CoMID structures that had associated extensions +// registered. +type Profile struct { + ID *eat.Profile + MapExtensions extensions.Map +} + +// GetComid returns a pointer to a new comid.Comid that had the Profile's +// extensions (if any) registered. +func (o *Profile) GetComid() *comid.Comid { + ret := comid.NewComid() + o.registerExtensions(ret, ComidMapExtensionPoints) + return ret +} + +// GetUnsignedCorim returns a pointer to a new UnsignedCorim that had the +// Profile's extensions (if any) registered. +func (o *Profile) GetUnsignedCorim() *UnsignedCorim { + ret := NewUnsignedCorim() + ret.Profile = o.ID + o.registerExtensions(ret, UnsignedCorimMapExtensionPoints) + return ret +} + +// GetSignedCorim returns a pointer to a new SignedCorim that had the +// Profile's extensions (if any) registered. +func (o *Profile) GetSignedCorim() *SignedCorim { + ret := NewSignedCorim() + ret.UnsignedCorim.Profile = o.ID + o.registerExtensions(ret, SignedCorimMapExtensionPoints) + return ret +} + +func (o *Profile) registerExtensions(e iextensible, points []extensions.Point) { + exts := extensions.NewMap() + for _, p := range points { + if v, ok := o.MapExtensions[p]; ok { + exts[p] = v + } + } + + if err := e.RegisterExtensions(exts); err != nil { + // exts is a subset of o.MapExtensions which have been + // validated when the profile was registered, so we should never + // get here. + panic(err) + } +} + +// RegisterProfile registers a set of extensions with the specified profile. If +// the profile has already been registered, or if the extensions are invalid, +// an error is returned. +func RegisterProfile(id *eat.Profile, exts extensions.Map) error { + strID, err := id.Get() + if err != nil { + return err + } + + if _, ok := profilesRegister[strID]; ok { + return fmt.Errorf("profile with id %q already registered", strID) + } + + for p, v := range exts { + if _, ok := AllExtensionPoints[p]; !ok { + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + + if reflect.TypeOf(v).Kind() != reflect.Pointer { + return fmt.Errorf("attempting to register a non-pointer IMapValue for %q", p) + } + } + + profilesRegister[strID] = Profile{ID: id, MapExtensions: exts} + + return nil +} + +// UnregisterProfile ensures there are no extensions registered for the +// specified profile ID. Returns true if extensions were previously registered +// and have been removed, and false otherwise. +func UnregisterProfile(id *eat.Profile) bool { + if id == nil { + return false + } + + strID, err := id.Get() + if err != nil { + return false + } + + if _, ok := profilesRegister[strID]; ok { + delete(profilesRegister, strID) + return true + } + + return false +} + +// GetProfile returns the Profile associated with the specified ID, or an empty +// profile if no Profile has been registered for the id. The second return +// value indicates whether a profile for the ID has been found. +func GetProfile(id *eat.Profile) (Profile, bool) { + if id == nil { + return Profile{}, false + } + + strID, err := id.Get() + if err != nil { + return Profile{}, false + } + + prof, ok := profilesRegister[strID] + return prof, ok +} + +type iextensible interface { + RegisterExtensions(exts extensions.Map) error +} + +var profilesRegister = make(map[string]Profile) + +func init() { + for _, p := range SignedCorimMapExtensionPoints { + AllExtensionPoints[p] = true + } + + for _, p := range UnsignedCorimMapExtensionPoints { + AllExtensionPoints[p] = true + } + + for _, p := range ComidMapExtensionPoints { + AllExtensionPoints[p] = true + } +} diff --git a/corim/profiles_test.go b/corim/profiles_test.go new file mode 100644 index 00000000..4b4a917e --- /dev/null +++ b/corim/profiles_test.go @@ -0,0 +1,187 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" +) + +func TestProfile_registration(t *testing.T) { + exts := extensions.NewMap() + + err := RegisterProfile(&eat.Profile{}, exts) + assert.EqualError(t, err, "no valid EAT profile") + + p1, err := eat.NewProfile("1.2.3") + require.NoError(t, err) + + err = RegisterProfile(p1, exts) + assert.NoError(t, err) + + p2, err := eat.NewProfile("1.2.3") + require.NoError(t, err) + + err = RegisterProfile(p2, exts) + assert.EqualError(t, err, `profile with id "1.2.3" already registered`) + + ret := UnregisterProfile(p2) + assert.True(t, ret) + ret = UnregisterProfile(p2) + assert.False(t, ret) + ret = UnregisterProfile(nil) + assert.False(t, ret) + + err = RegisterProfile(p2, exts) + assert.NoError(t, err) + + prof, ok := GetProfile(p1) + assert.True(t, ok) + assert.Equal(t, exts, prof.MapExtensions) + + _, ok = GetProfile(&eat.Profile{}) + assert.False(t, ok) + + p3, err := eat.NewProfile("2.3.4") + require.NoError(t, err) + + exts2 := extensions.NewMap().Add(extensions.Point("test"), &struct{}{}) + err = RegisterProfile(p3, exts2) + assert.EqualError(t, err, `unexpected extension point: "test"`) + + exts3 := extensions.NewMap().Add(ExtEntity, struct{}{}) + err = RegisterProfile(p3, exts3) + assert.EqualError(t, err, `attempting to register a non-pointer IMapValue for "CorimEntity"`) + + UnregisterProfile(p1) +} + +func TestProfile_getters(t *testing.T) { + id, err := eat.NewProfile("1.2.3") + require.NoError(t, err) + + profile := Profile{ + ID: id, + MapExtensions: extensions.NewMap(). + Add(comid.ExtComid, &struct{}{}). + Add(ExtUnsignedCorim, &struct{}{}). + Add(ExtSigner, &struct{}{}), + } + + c := profile.GetComid() + assert.NotNil(t, c.Extensions.IMapValue) + + u := profile.GetUnsignedCorim() + assert.NotNil(t, u.Extensions.IMapValue) + + s := profile.GetSignedCorim() + assert.NotNil(t, s.UnsignedCorim.Extensions.IMapValue) + assert.NotNil(t, s.Meta.Signer.Extensions.IMapValue) +} + +func TestProfile_marshaling(t *testing.T) { + type corimExtensions struct { + Extension1 *string `cbor:"-1,keyasint,omitempty" json:"ext1,omitempty"` + } + + type entityExtensions struct { + Address *string `cbor:"-1,keyasint,omitempty" json:"address,omitempty"` + } + + type refValExtensions struct { + Timestamp *int `cbor:"-1,keyasint,omitempty" json:"timestamp,omitempty"` + } + + profID, err := eat.NewProfile("http://example.com/test-profile") + require.NoError(t, err) + + extMap := extensions.NewMap(). + Add(ExtUnsignedCorim, &corimExtensions{}). + Add(comid.ExtEntity, &entityExtensions{}). + Add(comid.ExtReferenceValue, &refValExtensions{}) + err = RegisterProfile(profID, extMap) + require.NoError(t, err) + + c, err := UnmarshalUnsignedCorimFromCBOR(testGoodUnsignedCorimCBOR) + assert.NoError(t, err) + assert.Nil(t, c.Profile) + + c, err = UnmarshalUnsignedCorimFromCBOR(testUnsignedCorimWithExtensionsCBOR) + assert.NoError(t, err) + + assert.Equal(t, profID, c.Profile) + assert.Equal(t, "foo", c.Extensions.MustGetString("Extension1")) + + profile, ok := GetProfile(c.Profile) + assert.True(t, ok) + + cmd, err := UnmarshalComidFromCBOR(c.Tags[0], c.Profile) + assert.NoError(t, err) + + address := cmd.Entities.Values[0].Extensions.MustGetString("Address") + assert.Equal(t, "123 Fake Street", address) + + ts := cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. + Val.Extensions.MustGetInt("timestamp") + assert.Equal(t, 1720782190, ts) + + unregProfID, err := eat.NewProfile("http://example.com") + require.NoError(t, err) + + cmdNoExt, err := UnmarshalComidFromCBOR(c.Tags[0], unregProfID) + assert.NoError(t, err) + + address = cmdNoExt.Entities.Values[0].Extensions.MustGetString("Address") + assert.Equal(t, "", address) + + out, err := c.ToCBOR() + assert.NoError(t, err) + assertCoRIMEq(t, testUnsignedCorimWithExtensionsCBOR, out) + + out, err = cmd.ToCBOR() + assert.NoError(t, err) + // the first 3 bytes in Tags[0] is the tag indicating CoRIM + assertCBOREq(t, c.Tags[0][3:], out) + + c, err = UnmarshalUnsignedCorimFromJSON(testUnsignedCorimJSON) + assert.NoError(t, err) + assert.Nil(t, c.Profile) + + c, err = UnmarshalUnsignedCorimFromJSON(testUnsignedCorimWithExtensionsJSON) + assert.NoError(t, err) + + assert.Equal(t, profID, c.Profile) + assert.Equal(t, "foo", c.Extensions.MustGetString("Extension1")) + + cmd = profile.GetComid() + err = cmd.FromJSON(testComidJSON) + assert.NoError(t, err) + + cmd = profile.GetComid() + err = cmd.FromJSON(testComidWithExtensionsJSON) + assert.NoError(t, err) + + address = cmd.Entities.Values[0].Extensions.MustGetString("Address") + assert.Equal(t, "123 Fake Street", address) + + ts = cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. + Val.Extensions.MustGetInt("timestamp") + assert.Equal(t, 1720782190, ts) + + s, err := UnmarshalSignedCorimFromCBOR(testGoodSignedCorimCBOR) + assert.NoError(t, err) + assert.Nil(t, s.UnsignedCorim.Profile) + + s, err = UnmarshalSignedCorimFromCBOR(testSignedCorimWithExtensionsCBOR) + assert.NoError(t, err) + + assert.Equal(t, profID, s.UnsignedCorim.Profile) + assert.Equal(t, "foo", s.UnsignedCorim.Extensions.MustGetString("Extension1")) + + UnregisterProfile(profID) +} diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 32d2acfc..3d7b2158 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -28,6 +28,11 @@ type SignedCorim struct { message *cose.Sign1Message } +// NewSignedCorim instantiates an empty SignedCorim +func NewSignedCorim() *SignedCorim { + return &SignedCorim{} +} + func (o *SignedCorim) RegisterExtensions(exts extensions.Map) error { unsignedExts := extensions.NewMap() diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index e6ba9e8d..290555e2 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -346,7 +346,7 @@ func TestSignedCorim_SignVerify_ok(t *testing.T) { var SignedCorimIn SignedCorim - SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorim) + SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) SignedCorimIn.Meta = *metaGood(t) cbor, err := SignedCorimIn.Sign(signer) @@ -373,7 +373,7 @@ func TestSignedCorim_SignVerify_fail_tampered(t *testing.T) { var SignedCorimIn SignedCorim - SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorim) + SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) cbor, err := SignedCorimIn.Sign(signer) assert.Nil(t, err) diff --git a/corim/testcases/comid-ext.json b/corim/testcases/comid-ext.json new file mode 100644 index 00000000..541113ec --- /dev/null +++ b/corim/testcases/comid-ext.json @@ -0,0 +1,51 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "address": "123 Fake Street", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurements": [ + { + "key": { + "type": "cca.platform-config-id", + "value": "cfg v1.0.0" + }, + "value": { + "timestamp": 1720782190, + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } + } + } + ] + } + ] + } +} + diff --git a/corim/testcases/comid.json b/corim/testcases/comid.json new file mode 100644 index 00000000..974a473b --- /dev/null +++ b/corim/testcases/comid.json @@ -0,0 +1,49 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurements": [ + { + "key": { + "type": "cca.platform-config-id", + "value": "cfg v1.0.0" + }, + "value": { + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } + } + } + ] + } + ] + } +} + diff --git a/corim/testcases/corim-ext.json b/corim/testcases/corim-ext.json new file mode 100644 index 00000000..a04fbe7c --- /dev/null +++ b/corim/testcases/corim-ext.json @@ -0,0 +1,18 @@ +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "profile": "http://example.com/test-profile", + "ext1": "foo", + "validity": { + "not-before": "2021-12-31T00:00:00Z", + "not-after": "2025-12-31T00:00:00Z" + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "acme.example", + "roles": [ + "manifestCreator" + ] + } + ] +} diff --git a/corim/testcases/corim.json b/corim/testcases/corim.json new file mode 100644 index 00000000..dfdc9801 --- /dev/null +++ b/corim/testcases/corim.json @@ -0,0 +1,16 @@ +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "validity": { + "not-before": "2021-12-31T00:00:00Z", + "not-after": "2025-12-31T00:00:00Z" + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "acme.example", + "roles": [ + "manifestCreator" + ] + } + ] +} diff --git a/corim/testcases/regen-from-src.sh b/corim/testcases/regen-from-src.sh new file mode 100644 index 00000000..f119e952 --- /dev/null +++ b/corim/testcases/regen-from-src.sh @@ -0,0 +1,30 @@ +#!/usr/bin/bash +# Copyright 2024 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 +set -e + +GEN_TESTCASE=$(go env GOPATH)/bin/gen-testcase + +if [[ ! -f ${GEN_TESTCASE} ]]; then + echo "installing gen-testcase" + go install github.com/veraison/gen-testcase@v0.0.1 +fi + +testcases=( + good-corim + example-corim + corim-with-extensions +) + +for case in "${testcases[@]}"; do + echo "generating unsigned-${case}.cbor" + + ${GEN_TESTCASE} "src/${case}.yaml" -o "unsigned-${case}.cbor" + + echo "generating signed-${case}.cbor" + + ${GEN_TESTCASE} -s src/ec-p256.jwk -m src/meta.yaml "src/${case}.yaml" \ + -o "signed-${case}.cbor" +done + +echo "done." diff --git a/corim/testcases/signed-corim-with-extensions.cbor b/corim/testcases/signed-corim-with-extensions.cbor new file mode 100644 index 0000000000000000000000000000000000000000..4bfca682959be82d3ace80585e5e20241a5f7759 GIT binary patch literal 681 zcmccA5)r$YQH{AIv7jI)GdZy&Ge1wiC^J_(IVr!0Bf@bJ<06KGQi zX$f$Q!@i2lS00sk+FhXVs@%Ra7j^WY6(Lo%vpLY z3mF=l7BVbmxXBa|p#XBbZYId(xv6<2V4v$|rsx_#fgxiGNF!5LP<~=cP-$LXYEknd zpw7s}j7d&DEU89%hI$695ejQ|3m$)ROR9&D8aMuG#jgp+9eCqdi19*gY(%hI+<&ro^}=XUVoX%7v{`;#_A{lU=b7` literal 0 HcmV?d00001 diff --git a/corim/testcases/signed-example-corim.cbor b/corim/testcases/signed-example-corim.cbor new file mode 100644 index 0000000000000000000000000000000000000000..f6c4f46185582023dc04983cbef0118c99ed3c42 GIT binary patch literal 684 zcmccA5)r$YQH{AIv7jI)GdZy&Ge1wiC^J_(IVr!0Bf@bJ!y<+fM`vGG1)q`>h2qTg zyv)3Gh3wQy#v2MH86_nJ#a88bl^K%Wupp9& zbqPamNosM4LUKOPWQEKW<_aZ{R-jI(CcWhRTo_xopeR2rGbdFcIW0e*u`!bI*iFV? zOPCs$;IfganSqf>A>Yu*SivnZJ5?dLq$oADgdr2=YCV>P42?|-85T3#WQvGT06A7S z6Xev~)Vva~hjlYkbPb@ukTC_Mktr)EKQSe!G%qi;sCf}kXXIkm2!%Df1&=?urC7uo zWb@>lluFdYMV9Mqvj5JjDE$8P#;zCHj7d&DEU89%hI$5!i4WOMRs8o7j+|FLt{{~*kv9)w17k`+P<{wYs-d2-o+&ZbLG55l zZkpM-?eA3oqFw9tk9i+@^g%XL*Y~+*+pC8DyqS&msCGDl?Jxj3(u4#%7B77+`r%Bt zSMlMW`*vp^@;CZ_bz8MXk;|czoVUtniBv%BU`S2P({*=ZT*wgMy!&yzUZS`0y34`m zKRR`bML67UZIrI>+WGeOt_ATkvQwu|i(?E@^bHWpEwP^LVEwLUd$-}~*C7!}=gRq3 ahkUZzmiM%D(}vv^il;v9vXJ3fUIGAX$P||V literal 0 HcmV?d00001 diff --git a/corim/testcases/signed-good-corim.cbor b/corim/testcases/signed-good-corim.cbor new file mode 100644 index 0000000000000000000000000000000000000000..50dae69118fff2b0e11127df304879307902dcb1 GIT binary patch literal 607 zcmccA5)r$YQH{AIv7jI)GdZy&Ge1wiC^J_(IVr!0Bf@bJ<06KGQiKQODWNK_;j8JHI>OZQ~U~9-0SAEXrgZ-0I z&NRcP532W8@!v~0a$fZ~#B9ctfS~*kmQ+JMV?9%1EP|TOl-xA4bKBpk{zbdi>mTzz z^yq_ZrmpXE&9+w!{dqGR?O~=nf=xF7I?RLw(-$v&F8bk2xL5JvpZj)aAM!W)e|1~6 zMUl&)lbpB8XNgoWq^9QSx;rs0WC(EH{kUE)(c5_4<>2!low~)C8W%G)GcYnSWWu6R z4_DkpIIL%RX??cHU7;?_?op6-M5RZ%RBV@;^u?8i&%EO;W4nYG@+VsK^0K69@7p-{ YZ_LhL%-gg3Zp8k+wv*8@y|VuS06A{>k^lez literal 0 HcmV?d00001 diff --git a/corim/testcases/src/corim-with-extensions.yaml b/corim/testcases/src/corim-with-extensions.yaml new file mode 100644 index 00000000..2f1a45f1 --- /dev/null +++ b/corim/testcases/src/corim-with-extensions.yaml @@ -0,0 +1,75 @@ +# minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate +# - profile (claim 3) has been set to "http://example.com/test-profile" +# - extension claim -1 has been added to corim +# - extension claim -1 has been added to comid entity +--- +0: test corim id +3: http://example.com/test-profile +-1: foo +1: +- encodedCBOR: + tag: 506 + value: + 0: en-GB + 1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== + 2: + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + -1: 123 Fake Street + 4: + 0: + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 + diff --git a/corim/testcases/src/ec-p256.jwk b/corim/testcases/src/ec-p256.jwk new file mode 100644 index 00000000..e3c07719 --- /dev/null +++ b/corim/testcases/src/ec-p256.jwk @@ -0,0 +1,9 @@ +{ + "kty": "EC", + "crv": "P-256", + "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", + "use": "enc", + "kid": "1" +} diff --git a/corim/testcases/src/example-corim.yaml b/corim/testcases/src/example-corim.yaml new file mode 100644 index 00000000..55d45847 --- /dev/null +++ b/corim/testcases/src/example-corim.yaml @@ -0,0 +1,78 @@ +# minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate +# - profile (claim 3) has been set to "http://example.com/example-profile" +# - extension claim -1 has been added to corim +# - extension claim -1 has been added to comid entity +# NOTE: this is the as corim-with-extensions.yaml, except for the profile +# string, which is differentiated so that the example (which uses init() +# to register the profile) doesn't clash with the tests. +--- +0: test corim id +3: http://example.com/example-profile +-1: foo +1: +- encodedCBOR: + tag: 506 + value: + 0: en-GB + 1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== + 2: + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + -1: 123 Fake Street + 4: + 0: + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 + diff --git a/corim/testcases/src/good-corim.yaml b/corim/testcases/src/good-corim.yaml new file mode 100644 index 00000000..4e61fee6 --- /dev/null +++ b/corim/testcases/src/good-corim.yaml @@ -0,0 +1,66 @@ +# minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate +--- +0: test corim id +1: +- encodedCBOR: + tag: 506 + value: + 0: en-GB + 1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== + 2: + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + 4: + 0: + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + diff --git a/corim/testcases/src/meta.yaml b/corim/testcases/src/meta.yaml new file mode 100644 index 00000000..abffebe3 --- /dev/null +++ b/corim/testcases/src/meta.yaml @@ -0,0 +1,13 @@ +--- +1: + 0: + tag: 1 + value: 1640908800 + 1: + tag: 1 + value: 1767139200 +0: + 1: + tag: 32 + value: https://acme.example + 0: ACME Ltd signing key diff --git a/corim/testcases/unsigned-corim-with-extensions.cbor b/corim/testcases/unsigned-corim-with-extensions.cbor new file mode 100644 index 0000000000000000000000000000000000000000..0e056df92a7d13cf5caff8df0858e92e82bb1894 GIT binary patch literal 514 zcmZ3&Tp^!PQc_^0ub*0xm|KvOs+XLft6!2@T%uc0l%JNFld6!MmY>hq7|D3-CgZOq z45_Jky6#Sl3mF2OcR#MzOY}BgcRBd{N2hKvrp6@ zOwLW!gPPCO%)rQ`kZ)*Ytl*ZIovILAQk0rn!m^N|v1uX0Vy3L1{KS-?(!9LXB8Hnx z5fKU?y}Fqom*uABl_ZvA=I7~Vrsx_#fgxiG$jatLjEk5Wn;0V$+MW83DmB;|vc*-O zv-x2Eq?9ww@acoLp=l52!%Df1&=?u zrC7uoWb@>lluFdYMV9Mqvj5JjDE$8P#;zCHiy(SZ0)p~GSW*r3jP*>3&;@fAQ*zVH z&TW6E`WNk5uYb(@(4!BsnYzBuHQQb_^ykfNw1+qgVh2mA0nkk*#F(Dq2y_k1j>Su# yi+(s0?p1vF=f2(9hy0EHU)@%1QRH&yBtIvi2 literal 0 HcmV?d00001 diff --git a/corim/testcases/unsigned-example-corim.cbor b/corim/testcases/unsigned-example-corim.cbor new file mode 100644 index 0000000000000000000000000000000000000000..0c715c8f24d0886906a5f8df3eb40b02d6b4d4dd GIT binary patch literal 517 zcmZ3&*ci!p>?Y%{B`gaW8k-g}EN03I%1=xQD$UDFEn>LI6cM42n4Fuco0(gXlbV~F zSCUwgnV+YdnWAd|1%`|%j?TWW&5MAlBNwxz8tED88L&nutl2Gi{K+lFBGw?AC+DP8 zq8=`?TxXO0cV0!|_op{@y~t)va`ItZq#%_xkvEU2v57H4q1~zfs8WNiAzNJaIhzmm zPf9t{44*!z-dn|gFX6~})#Hm87eN#f6=b>`p3KvJ^CPzNYaDu^$LPzdxj*kA*ocTHdpTfFqS=!Y}mUd4xh z?%SPx$lvJy)os-lMJ|UkT%wSiUzC}vkeR~R7|FQ!CgZOq45_Jky6#Sl3mF2OcR#MzOY}BgcRBd{ zN2hKvrpCog%?yl844ID3zOD*BB`JE0Hxx=TN=gcft@QO1lXFw`QY#X33vyCf7BVz8 zEo4~CaFZz_LIEVBn+X!nP0cGwEXmBz)6GoLHGl#`#uSi&Oj$wsi77#)d3mWt&5M9K zBNwwqD6H8nc>KvN#Uj=qn [!NOTE] +> Currently, only map extensions can be associated with profiles. Type choice +> and enum value extensions (described below) can only be registered globally. + ## Type Choice Extensions From fc4fa6f45aaa84c5a76d42c0a909a6b7ff84a27b Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 12 Jul 2024 17:17:43 +0100 Subject: [PATCH 046/110] refactor!: rename Entity.EntityName to Entity.Name Rename the EntityName field inside Entity to just Name to remove the unnecessary duplication and align with Go best practices. BREAKING CHANGE: field EntityName has been renamed to just Name inside both comid.Entity and corim.Entity. Signed-off-by: Sergei Trofimov --- comid/comid.go | 6 +++--- comid/entity.go | 16 ++++++++-------- comid/entity_test.go | 10 +++++----- corim/entity.go | 16 ++++++++-------- corim/entity_test.go | 18 +++++++++--------- corim/example_profile_test.go | 2 +- corim/extensions_test.go | 8 ++++---- corim/unsignedcorim.go | 2 +- corim/unsignedcorim_test.go | 6 +++--- 9 files changed, 42 insertions(+), 42 deletions(-) diff --git a/comid/comid.go b/comid/comid.go index f01108f2..116100f4 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -140,9 +140,9 @@ func (o *Comid) AddEntity(name string, regID *string, roles ...Role) *Comid { } e := Entity{ - EntityName: MustNewStringEntityName(name), - RegID: uri, - Roles: rs, + Name: MustNewStringEntityName(name), + RegID: uri, + Roles: rs, } if o.Entities == nil { diff --git a/comid/entity.go b/comid/entity.go index 72902578..72db4929 100644 --- a/comid/entity.go +++ b/comid/entity.go @@ -15,9 +15,9 @@ import ( // Entity stores an entity-map capable of CBOR and JSON serializations. type Entity struct { - EntityName *EntityName `cbor:"0,keyasint" json:"name"` - RegID *TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` - Roles Roles `cbor:"2,keyasint" json:"roles"` + Name *EntityName `cbor:"0,keyasint" json:"name"` + RegID *TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` + Roles Roles `cbor:"2,keyasint" json:"roles"` Extensions } @@ -41,13 +41,13 @@ func (o *Entity) GetExtensions() extensions.IMapValue { return o.Extensions.IMapValue } -// SetEntityName is used to set the EntityName field of Entity using supplied name -func (o *Entity) SetEntityName(name string) *Entity { +// SetName is used to set the Name field of Entity using supplied name +func (o *Entity) SetName(name string) *Entity { if o != nil { if name == "" { return nil } - o.EntityName = MustNewStringEntityName(name) + o.Name = MustNewStringEntityName(name) } return o } @@ -74,11 +74,11 @@ func (o *Entity) SetRoles(roles ...Role) *Entity { // Valid checks for validity of the fields within each Entity func (o Entity) Valid() error { - if o.EntityName == nil { + if o.Name == nil { return fmt.Errorf("invalid entity: empty entity-name") } - if err := o.EntityName.Valid(); err != nil { + if err := o.Name.Valid(); err != nil { return fmt.Errorf("invalid entity: %w", err) } diff --git a/comid/entity_test.go b/comid/entity_test.go index 10ff2e13..3feade43 100644 --- a/comid/entity_test.go +++ b/comid/entity_test.go @@ -22,7 +22,7 @@ func TestEntity_Valid_empty(t *testing.T) { func TestEntity_Valid_name_but_no_roles(t *testing.T) { tv := Entity{} - require.NotNil(t, tv.SetEntityName("ACME Ltd.")) + require.NotNil(t, tv.SetName("ACME Ltd.")) err := tv.Valid() assert.EqualError(t, err, "invalid entity: empty roles") @@ -31,7 +31,7 @@ func TestEntity_Valid_name_but_no_roles(t *testing.T) { func TestEntity_Valid_name_regid_but_no_roles(t *testing.T) { tv := Entity{} - require.NotNil(t, tv.SetEntityName("ACME Ltd.")) + require.NotNil(t, tv.SetName("ACME Ltd.")) require.NotNil(t, tv.SetRegID("https://acme.example")) err := tv.Valid() @@ -41,7 +41,7 @@ func TestEntity_Valid_name_regid_but_no_roles(t *testing.T) { func TestEntity_Valid_name_regid_and_roles(t *testing.T) { tv := Entity{} - require.NotNil(t, tv.SetEntityName("ACME Ltd.")) + require.NotNil(t, tv.SetName("ACME Ltd.")) require.NotNil(t, tv.SetRegID("https://acme.example")) require.NotNil(t, tv.SetRoles(RoleTagCreator)) @@ -62,7 +62,7 @@ func TestEntities_Valid_ok(t *testing.T) { e := &Entity{} require.NotNil(t, - e.SetEntityName("ACME Ltd."). + e.SetName("ACME Ltd."). SetRegID("https://acme.example"). SetRoles(RoleTagCreator, RoleCreator), ) @@ -77,7 +77,7 @@ func TestEntities_Valid_ok(t *testing.T) { func TestEntity_SetEntityName_empty(t *testing.T) { e := Entity{} - assert.Nil(t, e.SetEntityName("")) + assert.Nil(t, e.SetName("")) } func TestEntity_SetRegID_empty(t *testing.T) { diff --git a/corim/entity.go b/corim/entity.go index d6129a19..00e6daac 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -16,9 +16,9 @@ import ( // Entity stores an entity-map capable of CBOR and JSON serializations. type Entity struct { - EntityName *EntityName `cbor:"0,keyasint" json:"name"` - RegID *comid.TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` - Roles Roles `cbor:"2,keyasint" json:"roles"` + Name *EntityName `cbor:"0,keyasint" json:"name"` + RegID *comid.TaggedURI `cbor:"1,keyasint,omitempty" json:"regid,omitempty"` + Roles Roles `cbor:"2,keyasint" json:"roles"` Extensions } @@ -46,14 +46,14 @@ func (o *Entity) GetExtensions() extensions.IMapValue { return o.Extensions.IMapValue } -// SetEntityName is used to set the EntityName field of Entity using supplied name -func (o *Entity) SetEntityName(name any) *Entity { +// SetName is used to set the EntityName field of Entity using supplied name +func (o *Entity) SetName(name any) *Entity { if o != nil { if name == "" { return nil } - o.EntityName = MustNewStringEntityName(name) + o.Name = MustNewStringEntityName(name) } return o } @@ -87,11 +87,11 @@ func (o *Entity) SetRoles(roles ...Role) *Entity { // Valid checks for validity of the fields within each Entity func (o Entity) Valid() error { - if o.EntityName == nil { + if o.Name == nil { return fmt.Errorf("invalid entity: empty entity-name") } - if err := o.EntityName.Valid(); err != nil { + if err := o.Name.Valid(); err != nil { return fmt.Errorf("invalid entity: %w", err) } diff --git a/corim/entity_test.go b/corim/entity_test.go index be23a0d4..893ae92d 100644 --- a/corim/entity_test.go +++ b/corim/entity_test.go @@ -23,7 +23,7 @@ func TestEntity_Valid_uninitialized(t *testing.T) { func TestEntity_Valid_empty_name(t *testing.T) { tv := Entity{ - EntityName: MustNewStringEntityName(""), + Name: MustNewStringEntityName(""), } err := tv.Valid() @@ -35,8 +35,8 @@ func TestEntity_Valid_non_nil_empty_URI(t *testing.T) { emptyRegID := comid.TaggedURI("") tv := Entity{ - EntityName: MustNewStringEntityName("ACME Ltd."), - RegID: &emptyRegID, + Name: MustNewStringEntityName("ACME Ltd."), + RegID: &emptyRegID, } err := tv.Valid() @@ -48,8 +48,8 @@ func TestEntity_Valid_missing_roles(t *testing.T) { regID := comid.TaggedURI("http://acme.example") tv := Entity{ - EntityName: MustNewStringEntityName("ACME Ltd."), - RegID: ®ID, + Name: MustNewStringEntityName("ACME Ltd."), + RegID: ®ID, } err := tv.Valid() @@ -61,9 +61,9 @@ func TestEntity_Valid_unknown_role(t *testing.T) { regID := comid.TaggedURI("http://acme.example") tv := Entity{ - EntityName: MustNewStringEntityName("ACME Ltd."), - RegID: ®ID, - Roles: Roles{Role(666)}, + Name: MustNewStringEntityName("ACME Ltd."), + RegID: ®ID, + Roles: Roles{Role(666)}, } err := tv.Valid() @@ -73,7 +73,7 @@ func TestEntity_Valid_unknown_role(t *testing.T) { func TestEntities_Valid_ok(t *testing.T) { e := NewEntity(). - SetEntityName("ACME Ltd."). + SetName("ACME Ltd."). SetRegID("http://acme.example"). SetRoles(RoleManifestCreator) require.NotNil(t, e) diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index 56e388f7..becff460 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -99,7 +99,7 @@ func Example_profile_unmarshal() { } fmt.Printf("Language: %s\n", *extractedComid.Language) - fmt.Printf("Entity: %s\n", *extractedComid.Entities.Values[0].EntityName) + fmt.Printf("Entity: %s\n", *extractedComid.Entities.Values[0].Name) fmt.Printf(" %s\n", extractedComid.Entities.Values[0]. Extensions.MustGetString("Address")) diff --git a/corim/extensions_test.go b/corim/extensions_test.go index 0c76235c..d0a57399 100644 --- a/corim/extensions_test.go +++ b/corim/extensions_test.go @@ -18,7 +18,7 @@ type TestExtensions struct { } func (o TestExtensions) ConstrainEntity(ent *Entity) error { - if ent.EntityName.String() != "Futurama" { + if ent.Name.String() != "Futurama" { return errors.New(`EntityName must be "Futurama"`) // nolint:golint } @@ -35,7 +35,7 @@ func (o TestExtensions) ConstrainSigner(_ *Signer) error { func TestEntityExtensions_Valid(t *testing.T) { ent := NewEntity() - ent.SetEntityName("The Simpsons") + ent.SetName("The Simpsons") ent.SetRoles(RoleManifestCreator) err := ent.Valid() @@ -48,7 +48,7 @@ func TestEntityExtensions_Valid(t *testing.T) { err = ent.Valid() assert.EqualError(t, err, `EntityName must be "Futurama"`) - ent.SetEntityName("Futurama") + ent.SetName("Futurama") err = ent.Valid() assert.NoError(t, err) @@ -84,7 +84,7 @@ func TestEntityExtensions_CBOR(t *testing.T) { err = cbor.Unmarshal(data, &ent) assert.NoError(t, err) - assert.Equal(t, ent.EntityName.String(), "acme") + assert.Equal(t, ent.Name.String(), "acme") address, err := ent.Get("address") require.NoError(t, err) diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 50e33d2a..06a6307f 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -207,7 +207,7 @@ func (o *UnsignedCorim) SetRimValidity(notAfter time.Time, notBefore *time.Time) func (o *UnsignedCorim) AddEntity(name string, regID *string, roles ...Role) *UnsignedCorim { if o != nil { e := NewEntity(). - SetEntityName(name). + SetName(name). SetRoles(roles...) if regID != nil { diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index ff3ca173..7e338b77 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -260,9 +260,9 @@ func TestUnsignedCorim_AddEntity_full(t *testing.T) { Entities: &Entities{ Values: []Entity{ { - EntityName: MustNewStringEntityName(name), - Roles: Roles{role}, - RegID: &taggedRegID, + Name: MustNewStringEntityName(name), + Roles: Roles{role}, + RegID: &taggedRegID, }, }, }, From 840f644f4778b4a9d3cc0199a88b94c146e1854f Mon Sep 17 00:00:00 2001 From: setrofim Date: Tue, 6 Aug 2024 17:42:07 +0100 Subject: [PATCH 047/110] extensions.Collection fixes (#120) * fix(extensions): Collection.GetExtensions() return nil when empty Collection.GetExtensions() used to return is cached extensions map, even when that map was empty. However, the contract for IExtensible (which Collection implements) is that it should return nil when it has no extensions registered. signed-off-by: sergei trofimov * fix(extensions): propagate extensions to Collection values Ensure that when RegisterExtensions() is called on a non-empty collection, new extensions are propagated to the collection's existing values. Signed-off-by: Sergei Trofimov --------- Signed-off-by: sergei trofimov Signed-off-by: Sergei Trofimov --- extensions/collection.go | 13 +++++++++++++ extensions/collection_test.go | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/extensions/collection.go b/extensions/collection.go index 6f7ded7e..28a7099a 100644 --- a/extensions/collection.go +++ b/extensions/collection.go @@ -94,6 +94,10 @@ func (o *Collection[P, I]) Clear() { // GetExtensions returns the extensions IMapValue that has been registered with // the collection. func (o *Collection[P, I]) GetExtensions() IMapValue { + if o.valueExtensions.IsEmpty() { + return nil + } + return o.valueExtensions.Get() } @@ -130,6 +134,15 @@ func (o *Collection[P, I]) RegisterExtensions(exts Map) error { o.valueExtensions.Set(exts) + for i := 0; i < len(o.Values); i++ { + var vi I = &o.Values[i] + if vi.GetExtensions() == nil { + if err := vi.RegisterExtensions(exts); err != nil { + return fmt.Errorf("error at index %d: %w", i, err) + } + } + } + return nil } diff --git a/extensions/collection_test.go b/extensions/collection_test.go index 3af3b2b2..f886934c 100644 --- a/extensions/collection_test.go +++ b/extensions/collection_test.go @@ -160,6 +160,28 @@ func Test_Collection_Valid(t *testing.T) { assert.EqualError(t, c.Valid(), "error at index 1: test error") } +func Test_Collection_GetExtensions(t *testing.T) { + c := NewCollection[testExtensible]() + assert.Nil(t, c.GetExtensions()) + + err := c.RegisterExtensions(Map{testPoint: &testExtensions{}}) + require.NoError(t, err) + + assert.NotNil(t, c.GetExtensions()) +} + +func Test_Collection_RegisterExtensions(t *testing.T) { + c := NewCollection[testExtensible]() + + e := testExtensible{} + c.Add(&e) + + err := c.RegisterExtensions(Map{testPoint: &testExtensions{}}) + require.NoError(t, err) + + assert.NotNil(t, c.Values[0].GetExtensions()) +} + func Test_Map(t *testing.T) { m := NewMap() assert.Equal(t, 0, len(m)) From 2583a736dea40f327ce53ce837141e422905e5fc Mon Sep 17 00:00:00 2001 From: setrofim Date: Wed, 7 Aug 2024 17:18:08 +0100 Subject: [PATCH 048/110] fix: propagate ca_cert to OAuth2 authenticator (#122) Use ca_cert certs when configuring the OAuth2 authenticator, in addition to their current use in connection to the API service. Signed-off-by: Sergei Trofimov --- cocli/cmd/root.go | 1 + go.mod | 2 +- go.sum | 7 +++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cocli/cmd/root.go b/cocli/cmd/root.go index d3b3268b..6297d248 100644 --- a/cocli/cmd/root.go +++ b/cocli/cmd/root.go @@ -73,6 +73,7 @@ func initConfig() { "token_url": v.GetString("token_url"), "username": v.GetString("username"), "password": v.GetString("password"), + "ca_certs": v.GetStringSlice("ca_cert"), }) cobra.CheckErr(err) default: diff --git a/go.mod b/go.mod index 33c4d26e..d5d7f8ce 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.8.2 - github.com/veraison/apiclient v0.2.1-0.20240531100343-8a3a730a1e94 + github.com/veraison/apiclient v0.3.1-0.20240807160142-9141ad363e45 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7 github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca diff --git a/go.sum b/go.sum index dec21edf..d90ae3a0 100644 --- a/go.sum +++ b/go.sum @@ -149,6 +149,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -321,10 +322,8 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/veraison/apiclient v0.2.0 h1:QELvZ+eEfzh9v0ORe9B2UTMpiA7aONHpZIfwSfcRR6s= -github.com/veraison/apiclient v0.2.0/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A= -github.com/veraison/apiclient v0.2.1-0.20240531100343-8a3a730a1e94 h1:0d7vTs3K9Y4bskTtI3pvkFE0HiSHc4vWA3M6Fc0lWRM= -github.com/veraison/apiclient v0.2.1-0.20240531100343-8a3a730a1e94/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A= +github.com/veraison/apiclient v0.3.1-0.20240807160142-9141ad363e45 h1:o+gCzGtusXZOOXuJavzvpraSk8ify4U60Aq9r/4vOho= +github.com/veraison/apiclient v0.3.1-0.20240807160142-9141ad363e45/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff h1:r6I2eJL/z8dp5flsQIKHMeDjyV6UO8If3MaVBLvTjF4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7 h1:KcKzBthSrSZIUEWBjVvkuk/DE3PyYFbXZxhx5byGFtc= From be7ec48294794682efe017eeae95ea00f6aeac56 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 14 Aug 2024 05:12:00 +0100 Subject: [PATCH 049/110] fix(comid): bring ValueTriple into alignment with rev05 At rev00, for reference/endorsed value triples, the CoRIM spec associated an environment with one or more measurements, allowing for two layers of multiplicity. Since rev01, and up to (current) rev05, the spec removed the second layer, now associating an environment with exactly one measurement. This commit brings our implementation into alignment with the spec, replacing ValueTriple.Measurements collection with the single ValueTriple.Measurement. Signed-off-by: Sergei Trofimov --- cocli/cmd/testcases/psa-refval.cbor | Bin 417 -> 538 bytes cocli/cmd/testcases/signed-corim-invalid.cbor | Bin 166 -> 166 bytes .../signed-corim-valid-with-cots.cbor | Bin 2349 -> 2349 bytes cocli/cmd/testcases/signed-corim-valid.cbor | Bin 799 -> 918 bytes cocli/cmd/testcases/src/psa-refval.yaml | 88 +++--- .../cmd/testcases/src/signed-corim-valid.yaml | 84 ++--- cocli/cmd/testcases/src/test-comid.yaml | 88 +++--- cocli/cmd/testcases/test-comid.cbor | Bin 417 -> 538 bytes cocli/cmd/testcases/test-coswid.cbor | Bin 377 -> 377 bytes cocli/cmd/testcases/test-cots.cbor | Bin 1943 -> 1943 bytes comid/comid_test.go | 18 +- comid/example_cca_realm_refval_test.go | 20 +- comid/example_cca_refval_test.go | 73 ++--- comid/example_psa_refval_test.go | 46 +-- comid/example_test.go | 288 +++++------------- comid/measurement.go | 45 --- comid/test_vars.go | 281 ++++++++++------- comid/testcases/comid-1.cbor | Bin 0 -> 174 bytes comid/testcases/comid-2.cbor | Bin 0 -> 450 bytes comid/testcases/comid-3.cbor | Bin 0 -> 151 bytes comid/testcases/comid-design-cd.cbor | Bin 0 -> 607 bytes comid/testcases/comid-firmware-cd.cbor | Bin 0 -> 339 bytes comid/testcases/regen-from-src.sh | 21 ++ comid/testcases/src/comid-1.diag | 37 +++ comid/testcases/src/comid-2.diag | 97 ++++++ comid/testcases/src/comid-3.diag | 34 +++ comid/testcases/src/comid-design-cd.diag | 119 ++++++++ comid/testcases/src/comid-firmware-cd.diag | 80 +++++ comid/valuetriple.go | 29 +- comid/valuetriple_test.go | 2 +- corim/example_profile_test.go | 27 +- corim/profiles_test.go | 4 +- corim/testcases/comid-ext.json | 28 +- corim/testcases/comid.json | 26 +- .../signed-corim-with-extensions.cbor | Bin 681 -> 800 bytes corim/testcases/signed-example-corim.cbor | Bin 684 -> 803 bytes corim/testcases/signed-good-corim.cbor | Bin 607 -> 726 bytes .../testcases/src/corim-with-extensions.yaml | 92 +++--- corim/testcases/src/example-corim.yaml | 92 +++--- corim/testcases/src/good-corim.yaml | 86 +++--- .../unsigned-corim-with-extensions.cbor | Bin 514 -> 633 bytes corim/testcases/unsigned-example-corim.cbor | Bin 517 -> 636 bytes corim/testcases/unsigned-good-corim.cbor | Bin 440 -> 559 bytes 43 files changed, 1051 insertions(+), 754 deletions(-) create mode 100644 comid/testcases/comid-1.cbor create mode 100644 comid/testcases/comid-2.cbor create mode 100644 comid/testcases/comid-3.cbor create mode 100644 comid/testcases/comid-design-cd.cbor create mode 100644 comid/testcases/comid-firmware-cd.cbor create mode 100644 comid/testcases/regen-from-src.sh create mode 100644 comid/testcases/src/comid-1.diag create mode 100644 comid/testcases/src/comid-2.diag create mode 100644 comid/testcases/src/comid-3.diag create mode 100644 comid/testcases/src/comid-design-cd.diag create mode 100644 comid/testcases/src/comid-firmware-cd.diag diff --git a/cocli/cmd/testcases/psa-refval.cbor b/cocli/cmd/testcases/psa-refval.cbor index 928a9ac0c5baba0347d8fce477a646a14a326dd4..e37b711cb6beda921d18a320c0cc29817e076932 100644 GIT binary patch delta 112 zcmZ3;Jd0(57i)9VLWac?qjhI7?d9}*@aO905^anc>n+a delta 86 zcmbQmvXFU#7i(kFLWaeYI~k29^E0vuH7{bg$rQPmG0Dk?CDlmJP|sjupUT9&>Wqsf lhcjwS?9`m>&Bz1fd}7oPf~ZV!49X8-Ni_hfHJR9_3;?Z&8s-21 diff --git a/cocli/cmd/testcases/signed-corim-invalid.cbor b/cocli/cmd/testcases/signed-corim-invalid.cbor index 06847a2d781fcaec03fb4cdf00661d2875ee1718..53fff8fad1da85902d9c27cc916f97ddeca585bf 100644 GIT binary patch delta 95 zcmV-l0HFV-0j2?vA(1gKRRN*_!5V2)XMg~~8ez^z0H8sjSU}7Sn_CH;x?}X0k0vXT zT$g$#<|+5k=z5)*I+VgHr!BD$$%;bl55P?N(e<R)$h BD;59% delta 96 zcmV-m0H6P+0j2?vArJwg0Kpnz&PV_O!5V2)XMm9*Fh!t2pjbd85kWOoIuaDJv%H5T z(v3yiK``+iD8makm=FgaCRV8y1Oa1^BjMfCBSUV`KG e;khD}?~i(51Fj9rGtM8AOw8jEh%hvB?pfX~!6cUe delta 72 zcmV-O0Js0G60H)j?Fc|cPmfg-UiQGdp#U?VfVe?WwaTb(%Vl5!&8wTCCg(B+`9pTf eljeCr6`)c*g<0;@Lf_*ZQajLmzxAE{j+~maF+_aEkF=GnIl!-r$ z7#B@WW7J?}xXBc`c(M(nHhYqj4@kjeRz@9OrmUd+#FU`Yyu8$+MU%HPnojO#)L?;0 z^B_x4e#02Y%+$DOawU`NS7w>3H7D|ZsbMJCZo8#nS>slNlTvYalV)wW rRjL#DzIpQgg?wtOH*=bw`nhYt?`M@2j7M(o-o&2Nwf)ncwJ8?>`$>X| delta 308 zcmbQnKA&xZVm;#`#)DFsA?Xba2c;6vc`z_6VkmKR_H|Y8DM?W%&P>nC%u83uPOW6T zp-_@hQc_TCrLUiuoSUkbT9KGrkdwL~lBr@D3z{LUjf;WmbCA_BH8L_}!VK192#5)P z@x|?|laTbplN{$KKeYM7*ci#U_$K48B@=Hr2{tw@WLV6U6_lTt5>%R(ms&JAkV!|V zc@e`+rpU#NNlrd2sYZH+dIppGnN%i6GO9B!nykvCG5G_d>EuqPI4*|N)I42xC#J?l zll7TZC%Vg;alE*?|I%CXP()2TKO|FG*~;B i=k&J5{@vg0kYAd!)9&5}wm*H7O!^nzy*Q7za4!Hwvv)KA diff --git a/cocli/cmd/testcases/src/psa-refval.yaml b/cocli/cmd/testcases/src/psa-refval.yaml index 0fae32a4..9a5d71e7 100644 --- a/cocli/cmd/testcases/src/psa-refval.yaml +++ b/cocli/cmd/testcases/src/psa-refval.yaml @@ -22,39 +22,55 @@ # acme-implementation-id-000000001 1: ACME 2: RoadRunner - - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK + # acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK + # acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/cocli/cmd/testcases/src/signed-corim-valid.yaml b/cocli/cmd/testcases/src/signed-corim-valid.yaml index 1a8c4a67..513f2aeb 100644 --- a/cocli/cmd/testcases/src/signed-corim-valid.yaml +++ b/cocli/cmd/testcases/src/signed-corim-valid.yaml @@ -25,42 +25,54 @@ value: acme-implementation-id-000000001 1: ACME 2: RoadRunner - - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - - 0: + 0: + tag: 600 + value: acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - - 0: + 0: + tag: 600 + value: acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= 2: - 0: tag: 32 diff --git a/cocli/cmd/testcases/src/test-comid.yaml b/cocli/cmd/testcases/src/test-comid.yaml index 0fae32a4..9a5d71e7 100644 --- a/cocli/cmd/testcases/src/test-comid.yaml +++ b/cocli/cmd/testcases/src/test-comid.yaml @@ -22,39 +22,55 @@ # acme-implementation-id-000000001 1: ACME 2: RoadRunner - - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK + # acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK + # acme-implementation-id-000000001 + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/cocli/cmd/testcases/test-comid.cbor b/cocli/cmd/testcases/test-comid.cbor index ddb69dcc2cfb54984edf332a5aa81e9df1e62c44..42a79e38bb812fa3ceafc4f2550c66982ded5263 100644 GIT binary patch delta 85 zcmZ3;Jd1^M3Clu;=B9-ViziB}3o)iRI{UgZWd-FYrUaGd<)s!)bdH(GYCCa$919*b Re`_Y!F)Fh%H8U_W0RXX%8=U|E delta 35 rcmbQmvXGf|3Clu;#)g8V7fVRw6&0nVmZT<^E6G2=ppfYtmi8~5LFV_EHG@Pmc@^_h)~P3!=y5euaN delta 13 UcmbQvKb@a(@kY@m_KA!l03Kum<^TWy diff --git a/comid/comid_test.go b/comid/comid_test.go index 7aed60dd..a708b115 100644 --- a/comid/comid_test.go +++ b/comid/comid_test.go @@ -48,11 +48,11 @@ func Test_Comid_ToJSONPretty(t *testing.T) { Environment: Environment{ Instance: MustNewUUIDInstance(TestUUID), }, - Measurements: *NewMeasurements().Add(&Measurement{ + Measurement: Measurement{ Val: Mval{ RawValue: NewRawValue().SetBytes(MustHexDecode(t, "deadbeef")), }, - }), + }, }), } @@ -70,16 +70,14 @@ func Test_Comid_ToJSONPretty(t *testing.T) { "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" } }, - "measurements": [ - { - "value": { - "raw-value": { - "type": "bytes", - "value": "3q2+7w==" - } + "measurement": { + "value": { + "raw-value": { + "type": "bytes", + "value": "3q2+7w==" } } - ] + } } ] } diff --git a/comid/example_cca_realm_refval_test.go b/comid/example_cca_realm_refval_test.go index b2965eb7..fa17fb74 100644 --- a/comid/example_cca_realm_refval_test.go +++ b/comid/example_cca_realm_refval_test.go @@ -71,24 +71,8 @@ func extractRealmRefVal(rv ValueTriple) error { return fmt.Errorf("extracting realm instanceID: %w", err) } - measurements := rv.Measurements - - if err := extractMeasurements(measurements); err != nil { - return fmt.Errorf("extracting measurements: %w", err) - } - - return nil -} - -func extractMeasurements(m Measurements) error { - if len(m.Values) == 0 { - return fmt.Errorf("no measurements") - } - - for i, meas := range m.Values { - if err := extractMeasurement(meas); err != nil { - return fmt.Errorf("extracting measurement at index %d: %w", i, err) - } + if err := extractMeasurement(rv.Measurement); err != nil { + return fmt.Errorf("extracting measurement: %w", err) } return nil diff --git a/comid/example_cca_refval_test.go b/comid/example_cca_refval_test.go index 92d0d49d..1e6787b7 100644 --- a/comid/example_cca_refval_test.go +++ b/comid/example_cca_refval_test.go @@ -21,21 +21,24 @@ func Example_cca_refval() { } // output: - // ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - // Label: BL - // Version: 2.1.0 - // Digest: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 - // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - // Label: PRoT - // Version: 1.3.5 - // Digest: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f - // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - // Label: ARoT - // Version: 0.1.4 - // Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 - // Label: a non-empty (unique) label - // Raw value: 72617776616c75650a72617776616c75650a + //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + //Label: BL + //Version: 2.1.0 + //Digest: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 + //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + //Label: PRoT + //Version: 1.3.5 + //Digest: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f + //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + //Label: ARoT + //Version: 0.1.4 + //Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 + //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + //Label: a non-empty (unique) label + //Raw value: 72617776616c75650a72617776616c75650a } func extractCcaRefVals(c *Comid) error { @@ -54,35 +57,33 @@ func extractCcaRefVals(c *Comid) error { func extractCCARefVal(rv ValueTriple) error { class := rv.Environment.Class + m := rv.Measurement if err := extractImplementationID(class); err != nil { return fmt.Errorf("extracting impl-id: %w", err) } - for i, m := range rv.Measurements.Values { - if m.Key == nil { - return fmt.Errorf("missing mKey at index %d", i) + if m.Key == nil { + return fmt.Errorf("missing mKey") + } + if !m.Key.IsSet() { + return fmt.Errorf("mKey not set") + } + + switch t := m.Key.Value.(type) { + case *TaggedPSARefValID: + if err := extractSwMeasurement(m); err != nil { + return fmt.Errorf("extracting measurement: %w", err) } - if !m.Key.IsSet() { - return fmt.Errorf("mKey not set at index %d", i) + case *TaggedCCAPlatformConfigID: + if err := extractCCARefValID(m.Key); err != nil { + return fmt.Errorf("extracting cca-refval-id: %w", err) } - - switch t := m.Key.Value.(type) { - case *TaggedPSARefValID: - if err := extractSwMeasurement(m); err != nil { - return fmt.Errorf("extracting measurement at index %d: %w", i, err) - } - case *TaggedCCAPlatformConfigID: - if err := extractCCARefValID(m.Key); err != nil { - return fmt.Errorf("extracting cca-refval-id: %w", err) - } - if err := extractRawValue(m.Val.RawValue); err != nil { - return fmt.Errorf("extracting raw vlue: %w", err) - } - default: - return fmt.Errorf("unexpected Mkey type: %T", t) + if err := extractRawValue(m.Val.RawValue); err != nil { + return fmt.Errorf("extracting raw vlue: %w", err) } - + default: + return fmt.Errorf("unexpected Mkey type: %T", t) } return nil diff --git a/comid/example_psa_refval_test.go b/comid/example_psa_refval_test.go index 7fa78313..1d7c7bb0 100644 --- a/comid/example_psa_refval_test.go +++ b/comid/example_psa_refval_test.go @@ -21,19 +21,21 @@ func Example_psa_refval() { } // output: - // ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - // Label: BL - // Version: 2.1.0 - // Digest: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 - // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - // Label: PRoT - // Version: 1.3.5 - // Digest: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f - // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - // Label: ARoT - // Version: 0.1.4 - // Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 + //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + //Label: BL + //Version: 2.1.0 + //Digest: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 + //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + //Label: PRoT + //Version: 1.3.5 + //Digest: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f + //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + //Label: ARoT + //Version: 0.1.4 + //Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 } func extractRefVals(c *Comid) error { @@ -57,29 +59,13 @@ func extractPSARefVal(rv ValueTriple) error { return fmt.Errorf("extracting impl-id: %w", err) } - measurements := rv.Measurements - - if err := extractSwMeasurements(measurements); err != nil { + if err := extractSwMeasurement(rv.Measurement); err != nil { return fmt.Errorf("extracting measurements: %w", err) } return nil } -func extractSwMeasurements(m Measurements) error { - if len(m.Values) == 0 { - return fmt.Errorf("no measurements") - } - - for i, m := range m.Values { - if err := extractSwMeasurement(m); err != nil { - return fmt.Errorf("extracting measurement at index %d: %w", i, err) - } - } - - return nil -} - func extractSwMeasurement(m Measurement) error { if err := extractPSARefValID(m.Key); err != nil { return fmt.Errorf("extracting PSA refval id: %w", err) diff --git a/comid/example_test.go b/comid/example_test.go index d94b8fa8..ce9a36a5 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -4,6 +4,7 @@ package comid import ( + _ "embed" "fmt" "github.com/google/uuid" @@ -29,21 +30,18 @@ func Example_encode() { Instance: MustNewUEIDInstance(TestUEID), Group: MustNewUUIDGroup(TestUUID), }, - Measurements: *NewMeasurements(). - Add( - MustNewUUIDMeasurement(TestUUID). - SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). - SetSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). - SetFlagsTrue(FlagIsDebug). - SetFlagsFalse(FlagIsSecure). - SetSerialNumber("C02X70VHJHD5"). - SetUEID(TestUEID). - SetUUID(TestUUID). - SetMACaddr(MACaddr(TestMACaddr)). - SetIPaddr(TestIPaddr), - ), + Measurement: *MustNewUUIDMeasurement(TestUUID). + SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). + SetSVN(2). + AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + SetFlagsTrue(FlagIsDebug). + SetFlagsFalse(FlagIsSecure). + SetSerialNumber("C02X70VHJHD5"). + SetUEID(TestUEID). + SetUUID(TestUUID). + SetMACaddr(MACaddr(TestMACaddr)). + SetIPaddr(TestIPaddr), }, ). AddEndorsedValue( @@ -57,21 +55,18 @@ func Example_encode() { Instance: MustNewUEIDInstance(TestUEID), Group: MustNewUUIDGroup(TestUUID), }, - Measurements: *NewMeasurements(). - Add( - MustNewUUIDMeasurement(TestUUID). - SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). - SetMinSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). - SetFlagsTrue(FlagIsDebug). - SetFlagsFalse(FlagIsSecure, FlagIsConfigured). - SetSerialNumber("C02X70VHJHD5"). - SetUEID(TestUEID). - SetUUID(TestUUID). - SetMACaddr(MACaddr(TestMACaddr)). - SetIPaddr(TestIPaddr), - ), + Measurement: *MustNewUUIDMeasurement(TestUUID). + SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). + SetMinSVN(2). + AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + SetFlagsTrue(FlagIsDebug). + SetFlagsFalse(FlagIsSecure, FlagIsConfigured). + SetSerialNumber("C02X70VHJHD5"). + SetUEID(TestUEID). + SetUUID(TestUUID). + SetMACaddr(MACaddr(TestMACaddr)). + SetIPaddr(TestIPaddr), }, ). AddAttestVerifKey( @@ -107,8 +102,8 @@ func Example_encode() { } // Output: - //a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + //a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfaa200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfaa200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurement":{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurement":{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA() { @@ -122,19 +117,23 @@ func Example_encode_PSA() { SetVendor("ACME Ltd."). SetModel("RoadRunner 2.0"), }, - Measurements: *NewMeasurements(). - Add( - MustNewPSAMeasurement( - MustCreatePSARefValID( - TestSignerID, "BL", "5.0.5", - )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), - ). - Add( - MustNewPSAMeasurement( - MustCreatePSARefValID( - TestSignerID, "PRoT", "1.3.5", - )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), - ), + Measurement: *MustNewPSAMeasurement( + MustCreatePSARefValID( + TestSignerID, "BL", "5.0.5", + )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), + }, + ). + AddReferenceValue( + ValueTriple{ + Environment: Environment{ + Class: NewClassImplID(TestImplID). + SetVendor("ACME Ltd."). + SetModel("RoadRunner 2.0"), + }, + Measurement: *MustNewPSAMeasurement( + MustCreatePSARefValID( + TestSignerID, "PRoT", "1.3.5", + )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), }, ). AddAttestVerifKey( @@ -160,8 +159,8 @@ func Example_encode_PSA() { } // Output: - //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008282a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e30a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef0082a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e30a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurement":{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}},{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurement":{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA_attestation_verification() { @@ -381,28 +380,29 @@ func Example_decode_JSON() { // Output: OK } -func Example_decode_CBOR_1() { - // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-1.diag - in := []byte{ - 0xa3, 0x01, 0xa1, 0x00, 0x50, 0x3f, 0x06, 0xaf, 0x63, 0xa9, 0x3c, 0x11, - 0xe4, 0x97, 0x97, 0x00, 0x50, 0x56, 0x90, 0x77, 0x3f, 0x02, 0x81, 0xa3, - 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, - 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, - 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, - 0x81, 0x00, 0x04, 0xa1, 0x00, 0x81, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, - 0x25, 0x50, 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, - 0xab, 0x5b, 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x6f, 0x41, 0x43, 0x4d, 0x45, 0x20, - 0x52, 0x6f, 0x61, 0x64, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x03, 0x01, - 0x81, 0xa1, 0x01, 0xa2, 0x00, 0xa2, 0x00, 0x65, 0x31, 0x2e, 0x30, 0x2e, - 0x30, 0x01, 0x19, 0x40, 0x00, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x44, - 0xaa, 0x33, 0x6a, 0xf4, 0xcb, 0x14, 0xa8, 0x79, 0x43, 0x2e, 0x53, 0xdd, - 0x65, 0x71, 0xc7, 0xfa, 0x9b, 0xcc, 0xaf, 0xb7, 0x5f, 0x48, 0x82, 0x59, - 0x26, 0x2d, 0x6e, 0xa3, 0xa4, 0xd9, 0x1b, - } +var ( + // test cases are based on diag files here: + // https://github.com/ietf-rats-wg/draft-ietf-rats-corim/tree/main/cddl/examples + + //go:embed testcases/comid-1.cbor + testComid1 []byte + + //go:embed testcases/comid-2.cbor + testComid2 []byte + + //go:embed testcases/comid-design-cd.cbor + testComidDesignCD []byte + + //go:embed testcases/comid-firmware-cd.cbor + testComidFirmwareCD []byte + + //go:embed testcases/comid-3.cbor + testComid3 []byte +) +func Example_decode_CBOR_1() { comid := Comid{} - err := comid.FromCBOR(in) + err := comid.FromCBOR(testComid1) if err != nil { fmt.Printf("FAIL: %v", err) } else { @@ -413,50 +413,8 @@ func Example_decode_CBOR_1() { } func Example_decode_CBOR_2() { - // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-2.diag - in := []byte{ - 0xa3, 0x01, 0xa1, 0x00, 0x50, 0x3f, 0x06, 0xaf, 0x63, 0xa9, 0x3c, 0x11, - 0xe4, 0x97, 0x97, 0x00, 0x50, 0x56, 0x90, 0x77, 0x3f, 0x02, 0x81, 0xa3, - 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, - 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, - 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, - 0x81, 0x00, 0x04, 0xa2, 0x00, 0x83, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, - 0x25, 0x50, 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, - 0xab, 0x5b, 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x78, 0x18, 0x41, 0x43, 0x4d, 0x45, - 0x20, 0x52, 0x6f, 0x61, 0x64, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x20, - 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x03, 0x01, 0x81, 0xa1, - 0x01, 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x44, 0xaa, 0x33, 0x6a, - 0xf4, 0xcb, 0x14, 0xa8, 0x79, 0x43, 0x2e, 0x53, 0xdd, 0x65, 0x71, 0xc7, - 0xfa, 0x9b, 0xcc, 0xaf, 0xb7, 0x5f, 0x48, 0x82, 0x59, 0x26, 0x2d, 0x6e, - 0xa3, 0xa4, 0xd9, 0x1b, 0x82, 0xa1, 0x00, 0xa5, 0x00, 0xd8, 0x25, 0x50, - 0xa7, 0x1b, 0x3e, 0x38, 0x8d, 0x45, 0x4a, 0x05, 0x81, 0xf3, 0x52, 0xe5, - 0x8c, 0x83, 0x2c, 0x5c, 0x01, 0x6a, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x77, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, - 0x43, 0x6f, 0x79, 0x6f, 0x74, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x65, 0x64, 0x20, 0x4f, 0x53, 0x03, 0x02, 0x04, 0x00, 0x81, 0xa1, 0x01, - 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0xbb, 0x71, 0x19, 0x8e, 0xd6, - 0x0a, 0x95, 0xdc, 0x3c, 0x61, 0x9e, 0x55, 0x5c, 0x2c, 0x0b, 0x8d, 0x75, - 0x64, 0xa3, 0x80, 0x31, 0xb0, 0x34, 0xa1, 0x95, 0x89, 0x25, 0x91, 0xc6, - 0x53, 0x65, 0xb0, 0x82, 0xa1, 0x00, 0xa5, 0x00, 0xd8, 0x25, 0x50, 0xa7, - 0x1b, 0x3e, 0x38, 0x8d, 0x45, 0x4a, 0x05, 0x81, 0xf3, 0x52, 0xe5, 0x8c, - 0x83, 0x2c, 0x5c, 0x01, 0x6a, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x02, 0x77, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, 0x43, - 0x6f, 0x79, 0x6f, 0x74, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, - 0x64, 0x20, 0x4f, 0x53, 0x03, 0x02, 0x04, 0x01, 0x81, 0xa1, 0x01, 0xa1, - 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0xbb, 0x71, 0x19, 0x8e, 0xd6, 0x0a, - 0x95, 0xdc, 0x3c, 0x61, 0x9e, 0x55, 0x5c, 0x2c, 0x0b, 0x8d, 0x75, 0x64, - 0xa3, 0x80, 0x31, 0xb0, 0x34, 0xa1, 0x95, 0x89, 0x25, 0x91, 0xc6, 0x53, - 0x65, 0xb0, 0x01, 0x81, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, 0x25, 0x50, - 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, 0xab, 0x5b, - 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x02, 0x72, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x03, - 0x00, 0x81, 0xa1, 0x01, 0xa1, 0x01, 0xd9, 0x02, 0x28, 0x01, - } - comid := Comid{} - err := comid.FromCBOR(in) + err := comid.FromCBOR(testComid2) if err != nil { fmt.Printf("FAIL: %v", err) } else { @@ -467,63 +425,8 @@ func Example_decode_CBOR_2() { } func Example_decode_CBOR_3() { - // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-design-cd.diag - in := []byte{ - 0xa4, 0x01, 0xa1, 0x00, 0x50, 0x1e, 0xac, 0xd5, 0x96, 0xf4, 0xa3, 0x4f, - 0xb6, 0x99, 0xbf, 0xae, 0xb5, 0x8e, 0x0a, 0x4e, 0x47, 0x02, 0x81, 0xa3, - 0x00, 0x71, 0x46, 0x50, 0x47, 0x41, 0x20, 0x44, 0x65, 0x73, 0x69, 0x67, - 0x6e, 0x73, 0x2d, 0x52, 0x2d, 0x55, 0x73, 0x01, 0xd8, 0x20, 0x78, 0x1e, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x70, 0x67, 0x61, - 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, 0x65, - 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x81, 0x00, 0x03, 0x81, 0xa2, - 0x00, 0x50, 0x97, 0xf5, 0xa7, 0x07, 0x1c, 0x6f, 0x43, 0x8f, 0x87, 0x7a, - 0x4a, 0x02, 0x07, 0x80, 0xeb, 0xe9, 0x01, 0x00, 0x04, 0xa2, 0x00, 0x84, - 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, - 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x01, 0x01, 0x76, 0x66, 0x70, 0x67, - 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, - 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, - 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x02, 0x01, 0x76, 0x66, 0x70, 0x67, - 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, - 0xa1, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x3f, 0xe1, 0x8e, 0xca, 0x40, - 0x53, 0x87, 0x9e, 0x01, 0x7e, 0xf5, 0xeb, 0x7a, 0x3e, 0x51, 0x57, 0x65, - 0x9c, 0x5f, 0x9b, 0xb1, 0x5b, 0x7d, 0x09, 0x95, 0x9b, 0x8b, 0x86, 0x47, - 0x82, 0x2a, 0x4c, 0xc2, 0x1c, 0x3a, 0xa6, 0x72, 0x1c, 0xef, 0x87, 0xf5, - 0xbf, 0xa5, 0x34, 0x95, 0xdb, 0x08, 0x33, 0x82, 0xa1, 0x00, 0xa3, 0x00, - 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, - 0x04, 0x03, 0x01, 0x76, 0x66, 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, - 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, - 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, 0xa1, 0x02, 0x81, 0x82, 0x07, - 0x58, 0x30, 0x20, 0xff, 0x68, 0x1a, 0x08, 0x82, 0xe2, 0x9b, 0x48, 0x19, - 0x53, 0x88, 0x89, 0x36, 0x20, 0x9c, 0xb5, 0x3d, 0xf9, 0xc5, 0xaa, 0xec, - 0x60, 0x6a, 0x2c, 0x24, 0xa0, 0xfb, 0x13, 0x85, 0x95, 0x12, 0x4b, 0x8e, - 0x3f, 0x24, 0xa1, 0x27, 0x71, 0xbc, 0x38, 0x54, 0xcc, 0x68, 0xb4, 0x03, - 0x61, 0xad, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, - 0x48, 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x01, 0x01, 0x76, - 0x66, 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, - 0x75, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x81, 0xa1, - 0x01, 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x58, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x58, 0x30, 0x46, - 0x62, 0x24, 0x34, 0x3d, 0x68, 0x18, 0x02, 0xc1, 0x50, 0x6b, 0xbe, 0xd7, - 0xd7, 0xf0, 0x0b, 0x96, 0x9b, 0xad, 0xdd, 0x63, 0x46, 0xe4, 0xf2, 0xe7, - 0xce, 0x14, 0x66, 0x92, 0x99, 0x6f, 0x22, 0xa4, 0x58, 0x14, 0xde, 0x81, - 0xd2, 0x48, 0xf5, 0x83, 0xb6, 0x5f, 0x81, 0x7b, 0x5f, 0xce, 0xab, 0x01, - 0x81, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, 0x48, - 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x02, 0x01, 0x76, 0x66, - 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, - 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x81, 0xa1, 0x01, - 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - } - comid := Comid{} - err := comid.FromCBOR(in) + err := comid.FromCBOR(testComidDesignCD) if err != nil { fmt.Printf("FAIL: %v", err) } else { @@ -534,41 +437,8 @@ func Example_decode_CBOR_3() { } func Example_decode_CBOR_4() { - // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-firmware-cd.diag - in := []byte{ - 0xa3, 0x01, 0xa1, 0x00, 0x50, 0xaf, 0x1c, 0xd8, 0x95, 0xbe, 0x78, 0x4a, - 0xdb, 0xb7, 0xe9, 0xad, 0xd4, 0x4a, 0x65, 0xab, 0xf3, 0x02, 0x81, 0xa3, - 0x00, 0x71, 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x20, 0x4d, - 0x46, 0x47, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, 0xd8, 0x20, 0x78, 0x18, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x77, 0x6d, 0x66, - 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, - 0x02, 0x81, 0x00, 0x04, 0xa2, 0x00, 0x82, 0x82, 0xa1, 0x00, 0xa4, 0x01, - 0x70, 0x66, 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x67, 0x66, 0x77, 0x59, 0x5f, 0x6e, - 0x35, 0x78, 0x03, 0x00, 0x04, 0x00, 0x81, 0xa1, 0x01, 0xa2, 0x01, 0xd9, - 0x02, 0x28, 0x01, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x15, 0xe7, 0x7d, - 0x6f, 0x13, 0x32, 0x52, 0xf1, 0xdb, 0x70, 0x44, 0x90, 0x13, 0x13, 0x88, - 0x4f, 0x29, 0x77, 0xd2, 0x10, 0x9b, 0x33, 0xc7, 0x9f, 0x33, 0xe0, 0x79, - 0xbf, 0xc7, 0x88, 0x65, 0x25, 0x5c, 0x0f, 0xb7, 0x33, 0xc2, 0x40, 0xfd, - 0xda, 0x54, 0x4b, 0x82, 0x15, 0xd7, 0xb8, 0xf8, 0x15, 0x82, 0xa1, 0x00, - 0xa4, 0x01, 0x70, 0x66, 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x67, 0x66, 0x77, 0x58, - 0x5f, 0x6e, 0x35, 0x78, 0x03, 0x01, 0x04, 0x00, 0x81, 0xa1, 0x01, 0xa2, - 0x01, 0xd9, 0x02, 0x28, 0x01, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x3d, - 0x90, 0xb6, 0xbf, 0x00, 0x3d, 0xa2, 0xd9, 0x4e, 0xa5, 0x46, 0x3f, 0x97, - 0xfb, 0x3c, 0x53, 0xdd, 0xc5, 0x1c, 0xfb, 0xa1, 0xe3, 0xe3, 0x8e, 0xef, - 0x7a, 0xf0, 0x71, 0xa6, 0x79, 0x86, 0x59, 0x5d, 0x22, 0x72, 0x91, 0x31, - 0xdf, 0x9f, 0xe8, 0x0f, 0x54, 0x51, 0xee, 0xf1, 0x54, 0xf8, 0x5e, 0x01, - 0x81, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, 0x48, - 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x01, 0x01, 0x70, 0x66, - 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x81, 0xa1, 0x01, 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, - 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - } - comid := Comid{} - err := comid.FromCBOR(in) + err := comid.FromCBOR(testComidFirmwareCD) if err != nil { fmt.Printf("FAIL: %v", err) } else { @@ -579,24 +449,8 @@ func Example_decode_CBOR_4() { } func Example_decode_CBOR_5() { - // Taken from https://github.com/ietf-corim-cddl/blob/main/examples/comid-3.diag - in := []byte{ - 0xa3, 0x01, 0xa1, 0x00, 0x78, 0x20, 0x6d, 0x79, 0x2d, 0x6e, 0x73, 0x3a, - 0x61, 0x63, 0x6d, 0x65, 0x2d, 0x72, 0x6f, 0x61, 0x64, 0x72, 0x75, 0x6e, - 0x6e, 0x65, 0x72, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x02, 0x81, 0xa3, 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x01, 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, - 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x83, 0x01, 0x00, 0x02, 0x04, 0xa1, 0x00, - 0x81, 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x44, 0x55, 0x02, 0xc0, - 0x00, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x02, 0x78, 0x18, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x52, 0x6f, 0x61, 0x64, - 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x20, 0x46, 0x69, 0x72, 0x6d, 0x77, - 0x61, 0x72, 0x65, 0x81, 0xa2, 0x00, 0x19, 0x02, 0xbc, 0x01, 0xa1, 0x02, - 0x81, 0x82, 0x06, 0x44, 0xab, 0xcd, 0xef, 0x00, - } comid := Comid{} - err := comid.FromCBOR(in) + err := comid.FromCBOR(testComid3) if err != nil { fmt.Printf("FAIL: %v", err) } else { diff --git a/comid/measurement.go b/comid/measurement.go index ac626246..15d511e5 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -773,48 +773,3 @@ func (o Measurement) Valid() error { return o.Val.Valid() } - -// Measurements is a container for Measurement instances and their extensions. -// It is a thin wrapper around extensions.Collection. -type Measurements extensions.Collection[Measurement, *Measurement] - -func NewMeasurements() *Measurements { - return (*Measurements)(extensions.NewCollection[Measurement]()) -} - -func (o *Measurements) RegisterExtensions(exts extensions.Map) error { - return (*extensions.Collection[Measurement, *Measurement])(o).RegisterExtensions(exts) -} - -func (o *Measurements) GetExtensions() extensions.IMapValue { - return (*extensions.Collection[Measurement, *Measurement])(o).GetExtensions() -} - -func (o *Measurements) Valid() error { - return (*extensions.Collection[Measurement, *Measurement])(o).Valid() -} - -func (o *Measurements) IsEmpty() bool { - return (*extensions.Collection[Measurement, *Measurement])(o).IsEmpty() -} - -func (o *Measurements) Add(val *Measurement) *Measurements { - ret := (*extensions.Collection[Measurement, *Measurement])(o).Add(val) - return (*Measurements)(ret) -} - -func (o Measurements) MarshalCBOR() ([]byte, error) { - return (extensions.Collection[Measurement, *Measurement])(o).MarshalCBOR() -} - -func (o *Measurements) UnmarshalCBOR(data []byte) error { - return (*extensions.Collection[Measurement, *Measurement])(o).UnmarshalCBOR(data) -} - -func (o Measurements) MarshalJSON() ([]byte, error) { - return (extensions.Collection[Measurement, *Measurement])(o).MarshalJSON() -} - -func (o *Measurements) UnmarshalJSON(data []byte) error { - return (*extensions.Collection[Measurement, *Measurement])(o).UnmarshalJSON(data) -} diff --git a/comid/test_vars.go b/comid/test_vars.go index ce3b59fa..8b993e6d 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -234,53 +234,75 @@ var ( "model": "RoadRunner" } }, - "measurements": [ - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "BL", - "version": "2.1.0", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, + "measurement": { + "key": { + "type": "psa.refval-id", "value": { - "digests": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - ] + "label": "BL", + "version": "2.1.0", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" } }, - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "PRoT", - "version": "1.3.5", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } + "value": { + "digests": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + ] + } + } + }, + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurement": { + "key": { + "type": "psa.refval-id", "value": { - "digests": [ - "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" - ] + "label": "PRoT", + "version": "1.3.5", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" } }, - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "ARoT", - "version": "0.1.4", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } + "value": { + "digests": [ + "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" + ] + } + } + }, + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurement": { + "key": { + "type": "psa.refval-id", "value": { - "digests": [ - "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" - ] + "label": "ARoT", + "version": "0.1.4", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" } + }, + "value": { + "digests": [ + "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" + ] } - ] + } } ] } @@ -349,7 +371,7 @@ var ( } } ` - CCARefValJSONTemplate = `{ + CCARefValJSONTemplate = ` { "lang": "en-GB", "tag-identity": { "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", @@ -375,65 +397,99 @@ var ( "model": "RoadRunner" } }, - "measurements": [ - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "BL", - "version": "2.1.0", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, + "measurement": { + "key": { + "type": "psa.refval-id", "value": { - "digests": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - ] + "label": "BL", + "version": "2.1.0", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" } }, - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "PRoT", - "version": "1.3.5", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } + "value": { + "digests": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + ] + } + } + }, + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurement": { + "key": { + "type": "psa.refval-id", "value": { - "digests": [ - "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" - ] + "label": "PRoT", + "version": "1.3.5", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" } }, - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "ARoT", - "version": "0.1.4", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } + "value": { + "digests": [ + "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" + ] + } + } + }, + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurement": { + "key": { + "type": "psa.refval-id", "value": { - "digests": [ - "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" - ] + "label": "ARoT", + "version": "0.1.4", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" } }, - { - "key": { - "type": "cca.platform-config-id", - "value": "a non-empty (unique) label" + "value": { + "digests": [ + "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" + ] + } + } + }, + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" }, - "value": { - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" - } + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurement": { + "key": { + "type": "cca.platform-config-id", + "value": "a non-empty (unique) label" + }, + "value": { + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" } } - ] + } } ] } @@ -444,36 +500,35 @@ var ( "tag-identity": { "id": "99019224-57AA-44BC-BEF8-D36BDD6BD035", "version": 0 -}, -"entities": [ - { - "name": "Workload Client Ltd.", - "regid": "https://workloadclient.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } -], -"triples": { - "reference-values": [ + }, + "entities": [ { - "environment": { - "class": { - "id": { - "type": "uuid", - "value": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C" + "name": "Workload Client Ltd.", + "regid": "https://workloadclient.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "uuid", + "value": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C" + }, + "vendor": "Workload Client Ltd" }, - "vendor": "Workload Client Ltd" + "instance": { + "type": "bytes", + "value": "QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" + } }, - "instance": { - "type": "bytes", - "value": "QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" - } - }, - "measurements": [ - { + "measurement": { "value": { "raw-value": { "type": "bytes", @@ -513,9 +568,9 @@ var ( } } } - ] - } - ] + } + ] + } } -}` +` ) diff --git a/comid/testcases/comid-1.cbor b/comid/testcases/comid-1.cbor new file mode 100644 index 0000000000000000000000000000000000000000..033b8ddd0ab3c3e1f7631e9b23ad845aab029e56 GIT binary patch literal 174 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@Cx`E@D{3kZP!Bpl84+>A=9$*u)s2;Ihg%>&t186_w6ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@9G)9NJw=`vOgW(2~g;P6KCmst<-9V{L h2tN_BiLsHG;46ZLUVe!}ej3D6%pl(|-el5X1OT>wqEP?< literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-3.cbor b/comid/testcases/comid-3.cbor new file mode 100644 index 0000000000000000000000000000000000000000..f47d1d1a6138ce4c7572037280425c7c66f8ded7 GIT binary patch literal 151 zcmZ3?xR9YjA-7UDuh=RvIX6|eC_gc!s5CDxwMe(Pw4fj-H8(Y{gsE{cL#Cs%ud9M* zUa}tJ4TX}7l9GaAD}8;C9=+6x#9W|Cre;P4CYFT^jZF&~7Bk$)cL`-Wz`%&m$5bH! mHX{gVMG)8u1-Hzi-15Yt)I|)EOnVp?GBq}_xvW0>o&f-IFf(!h literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-design-cd.cbor b/comid/testcases/comid-design-cd.cbor new file mode 100644 index 0000000000000000000000000000000000000000..d736d6c5a7b1c70db5324001f6d149997af71a56 GIT binary patch literal 607 zcmZ3&xR4=0Zq3zcUl#jso4J48);=yjcc#Y0425n1?v4sBsl}P;dBwUxx}n94Hxw%5 zGD=DcimmkZ(+biPQy`K>rNw%w6^Xe8IjKyI49ty-7y_n$UCu6(@7&*B<;BF_@cJbq z1Ir?YmZpUaiy3a@dndGcFt+{hW#ngJWGusO5Hr(4#zibQnG8G_pn%omKM;U8n8q;S zGiD)EV-tIXf&Ih2Qx3uH^BC*CzOJ$h3{RaCKYL?zE$7tP-EHnoT0VzltdG?U4v`I7bm6k+^+-tn#@wItdd}DR|xz&t~SX_oPm~qDy057lAq5uE@ literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-firmware-cd.cbor b/comid/testcases/comid-firmware-cd.cbor new file mode 100644 index 0000000000000000000000000000000000000000..cb876a6e521693fb0928e2f13f69a06a746233f4 GIT binary patch literal 339 zcmZ3?xR4=Wz08fN`zpL{Z-2S=idX9D&rFSr84BGpi*m~oi&7PQ-P{#C^OE%#Zzxnq zWR#Q?6kF-*rV z@b^}Tca!M#9X~{ITMz-ZfDzpS+X>tDGuSS=>9^F)e)?~l;JZg /dev/null && pwd ) + +if [[ "$(type -p diag2cbor.rb)" == "" ]]; then + echo "ERROR: please install ruby-cbor-diag package" + exit 1 +fi + +for case in "$THIS_DIR"/src/*.diag; do + outfile=$(basename "${case%%.diag}").cbor + + echo "generating $outfile" + + diag2cbor.rb "$case" > "$THIS_DIR/$outfile" +done + +echo "done." diff --git a/comid/testcases/src/comid-1.diag b/comid/testcases/src/comid-1.diag new file mode 100644 index 00000000..c756da18 --- /dev/null +++ b/comid/testcases/src/comid-1.diag @@ -0,0 +1,37 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + / measurement-map / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ] ] + } +} diff --git a/comid/testcases/src/comid-2.diag b/comid/testcases/src/comid-2.diag new file mode 100644 index 00000000..ce3c2752 --- /dev/null +++ b/comid/testcases/src/comid-2.diag @@ -0,0 +1,97 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner Firmware", + / comid.layer / 3 : 1 + } + }, + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ], + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'a71b3e388d454a0581f352e58c832c5c' + ), + / comid.vendor / 1 : "WYLIE Inc.", + / comid.model / 2 : "WYLIE Coyote Trusted OS", + / comid.layer / 3 : 2, + / comid.index / 4 : 0 + } + }, + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'bb71198ed60a95dc3c619e555c2c0b8d7564a38031b034a195892591c65365b0' + ] ] + } + } + ], + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'a71b3e388d454a0581f352e58c832c5c' + ), + / comid.vendor / 1 : "WYLIE Inc.", + / comid.model / 2 : "WYLIE Coyote Trusted OS", + / comid.layer / 3 : 2, + / comid.index / 4 : 1 + } + }, + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'bb71198ed60a95dc3c619e555c2c0b8d7564a38031b034a195892591c65365b0' + ] ] + } + } + ] + ], + / comid.endorsed-triples / 1 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME Root of Trust", + / comid.layer / 3 : 0 + } + }, + / measurement-map / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1) + } + } + ] ] + } +} diff --git a/comid/testcases/src/comid-3.diag b/comid/testcases/src/comid-3.diag new file mode 100644 index 00000000..c11fc237 --- /dev/null +++ b/comid/testcases/src/comid-3.diag @@ -0,0 +1,34 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : "my-ns:acme-roadrunner-supplement" + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 1,0,2 ] / creator, tag-creator, maintainer / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111( + h'5502C000' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner Firmware" + } + }, + / measurement-map / { + / comid.mkey / 0: 700, + / comid.mval / 1 : { + / comid.digests / 2 : [[ + / hash-alg-id / 6, / sha-256-32 / + / hash-value / h'ABCDEF00' ]] + } + } + ] + ] + } +} diff --git a/comid/testcases/src/comid-design-cd.diag b/comid/testcases/src/comid-design-cd.diag new file mode 100644 index 00000000..d3305b56 --- /dev/null +++ b/comid/testcases/src/comid-design-cd.diag @@ -0,0 +1,119 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'1EACD596F4A34FB699BFAEB58E0A4E47' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "FPGA Designs-R-Us", + / comid.reg-id / 1 : 32("https://fpgadesignsrus.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.linked-tags / 3 : [ { + / comid.linked-tag-id / 0 : h'97F5A7071C6F438F877A4A020780EBE9', + / comid.tag-rel / 1 : / comid.supplements / 0 + } + ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / ** Layer 3 device state ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0401'), / 2.16.840.1.113741.1.15.4.1 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + / measurement-map / + { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' + } + } + ], + [ + / environment-map / { + / ** Layer 2 design (IO descriptor) hash ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0402'), / 2.16.840.1.113741.1.15.4.2 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + / measurement-map / + { + / comid.mval / 1 : { + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'3FE18ECA4053879E017EF5EB7A3E5157659C5F9BB15B7D09959B8B8647822A4CC21C3AA6721CEF87F5BFA53495DB0833' + ] + ] + } + } + ], + [ + / environment-map / { + / ** Layer 2 design (CORE descriptor) hash ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0403'), / 2.16.840.1.113741.1.15.4.3 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + / measurement-map / + { + / comid.mval / 1 : { + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'20FF681A0882E29B481953888936209CB53DF9C5AAEC606A2C24A0FB138595124B8E3F24A12771BC3854CC68B40361AD' + ] + ] + } + } + ], + [ + / environment-map / { + / ** Firmware is valid (example assertion) ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "fpgadesignsrus.example" + } + }, + / measurement-map / + { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), + / comid.raw-value-mask / 5 : h'466224343D681802C1506BBED7D7F00B969BADDD6346E4F2E7CE146692996F22A45814DE81D248F583B65F817B5FCEAB' + } + } + ] + ], + / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / ** Design is valid (example assertion) ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046302'), / 2.16.840.1.113741.1.15.4.99.2 / + / comid.vendor / 1 : "fpgadesignsrus.example" + } + }, + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' + } + } + ] + ] + } + } diff --git a/comid/testcases/src/comid-firmware-cd.diag b/comid/testcases/src/comid-firmware-cd.diag new file mode 100644 index 00000000..1e8625fc --- /dev/null +++ b/comid/testcases/src/comid-firmware-cd.diag @@ -0,0 +1,80 @@ + + / concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'AF1CD895BE784ADBB7E9ADD44A65ABF3' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "Firmware MFG Inc.", + / comid.reg-id / 1 : 32("https://fwmfginc.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / ** Hash of layer 0 firmware ** / + / comid.class / 0 : { + / comid.vendor / 1 : "fwmfginc.example", + / comid.model / 2 : "fwY_n5x", + / comid.layer / 3 : 0, + / comid.index / 4 : 0 + } + }, + / measurement-map / + { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1), + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'15E77D6F133252F1DB7044901313884F2977D2109B33C79F33E079BFC78865255C0FB733C240FDDA544B8215D7B8F815' + ] + ] + } + } + ], + [ + / environment-map / { + / ** Hash of layer 1 firmware ** / + / comid.class / 0 : { + / comid.vendor / 1 : "fwmfginc.example", + / comid.model / 2 : "fwX_n5x", + / comid.layer / 3 : 1, + / comid.index / 4 : 0 + } + }, + / measurement-map / + { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1), + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'3D90B6BF003DA2D94EA5463F97FB3C53DDC51CFBA1E3E38EEF7AF071A67986595D22729131DF9FE80F5451EEF154F85E' + ] + ] + } + } + ] + ], + / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / comid.class / 0 : { + / ** Firmware is valid (example) ** / + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "fwmfginc.example" + } + }, + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' + } + } + ] + ] + } + } diff --git a/comid/valuetriple.go b/comid/valuetriple.go index a7662ea0..9e175ce8 100644 --- a/comid/valuetriple.go +++ b/comid/valuetriple.go @@ -4,29 +4,28 @@ package comid import ( - "errors" "fmt" "github.com/veraison/corim/extensions" ) -// ValueTriple relates measurements to a target environment, essentially -// forming a subject-predicate-object triple of "measurements-pertain -// to-environment". This structure is used to represent both -// reference-triple-record and endorsed-triple-record in the CoRIM spec (as of -// rev. 04). +// ValueTriple relates a measurement to a target environment, essentially +// forming a subject-predicate-object triple of +// "measurement-pertains-to-environment". This structure is used to represent +// both reference-triple-record and endorsed-triple-record in the CoRIM spec +// (as of rev. 04). type ValueTriple struct { - _ struct{} `cbor:",toarray"` - Environment Environment `json:"environment"` - Measurements Measurements `json:"measurements"` + _ struct{} `cbor:",toarray"` + Environment Environment `json:"environment"` + Measurement Measurement `json:"measurement"` } func (o *ValueTriple) RegisterExtensions(exts extensions.Map) error { - return o.Measurements.RegisterExtensions(exts) + return o.Measurement.RegisterExtensions(exts) } func (o *ValueTriple) GetExtensions() extensions.IMapValue { - return o.Measurements.GetExtensions() + return o.Measurement.GetExtensions() } func (o ValueTriple) Valid() error { @@ -34,12 +33,8 @@ func (o ValueTriple) Valid() error { return fmt.Errorf("environment validation failed: %w", err) } - if o.Measurements.IsEmpty() { - return errors.New("measurements validation failed: no measurement entries") - } - - if err := o.Measurements.Valid(); err != nil { - return fmt.Errorf("measurements validation failed: %w", err) + if err := o.Measurement.Valid(); err != nil { + return fmt.Errorf("measurement validation failed: %w", err) } return nil diff --git a/comid/valuetriple_test.go b/comid/valuetriple_test.go index 0888e32c..857bcf2b 100644 --- a/comid/valuetriple_test.go +++ b/comid/valuetriple_test.go @@ -19,5 +19,5 @@ func Test_ReferenceValue(t *testing.T) { require.NoError(t, err) rv.Environment.Instance = MustNewUUIDInstance(id) err = rv.Valid() - assert.EqualError(t, err, "measurements validation failed: no measurement entries") + assert.EqualError(t, err, "measurement validation failed: no measurement value set") } diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index becff460..b62a9da5 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -104,10 +104,10 @@ func Example_profile_unmarshal() { Extensions.MustGetString("Address")) fmt.Printf("Measurements:\n") - for _, m := range extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values { + for _, refVal := range extractedComid.Triples.ReferenceValues.Values { - val := hex.EncodeToString((*m.Val.Digests)[0].HashValue) - tsInt := m.Val.Extensions.MustGetInt64("timestamp") + val := hex.EncodeToString((*refVal.Measurement.Val.Digests)[0].HashValue) + tsInt := refVal.Measurement.Val.Extensions.MustGetInt64("timestamp") ts := time.Unix(tsInt, 0).UTC() fmt.Printf(" %v taken at %s\n", val, ts.Format("2006-01-02T15:04:05")) @@ -152,15 +152,6 @@ func Example_profile_marshal() { log.Fatalf("could not set entity Address: %v", err) } - refVal := comid.ValueTriple{ - Environment: comid.Environment{ - Class: comid.NewClassImplID(comid.TestImplID). - SetVendor("ACME Ltd."). - SetModel("RoadRunner 2.0"), - }, - Measurements: *comid.NewMeasurements(), - } - measurement := comid.MustNewPSAMeasurement( comid.MustCreatePSARefValID( comid.TestSignerID, "BL", "5.0.5", @@ -177,7 +168,15 @@ func Example_profile_marshal() { log.Fatal("could not register refval extensions") } - refVal.Measurements.Add(measurement) + refVal := comid.ValueTriple{ + Environment: comid.Environment{ + Class: comid.NewClassImplID(comid.TestImplID). + SetVendor("ACME Ltd."). + SetModel("RoadRunner 2.0"), + }, + Measurement: *measurement, + } + myComid.Triples.AddReferenceValue(refVal) err = myComid.Valid() @@ -195,5 +194,5 @@ func Example_profile_marshal() { fmt.Printf("corim: %v", hex.EncodeToString(buf)) // output: - // corim: a300f6018158d9d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 + // corim: a300f6018158d8d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e30a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 } diff --git a/corim/profiles_test.go b/corim/profiles_test.go index 4b4a917e..34c5e89e 100644 --- a/corim/profiles_test.go +++ b/corim/profiles_test.go @@ -126,7 +126,7 @@ func TestProfile_marshaling(t *testing.T) { address := cmd.Entities.Values[0].Extensions.MustGetString("Address") assert.Equal(t, "123 Fake Street", address) - ts := cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. + ts := cmd.Triples.ReferenceValues.Values[0].Measurement. Val.Extensions.MustGetInt("timestamp") assert.Equal(t, 1720782190, ts) @@ -169,7 +169,7 @@ func TestProfile_marshaling(t *testing.T) { address = cmd.Entities.Values[0].Extensions.MustGetString("Address") assert.Equal(t, "123 Fake Street", address) - ts = cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. + ts = cmd.Triples.ReferenceValues.Values[0].Measurement. Val.Extensions.MustGetInt("timestamp") assert.Equal(t, 1720782190, ts) diff --git a/corim/testcases/comid-ext.json b/corim/testcases/comid-ext.json index 541113ec..a670c1ca 100644 --- a/corim/testcases/comid-ext.json +++ b/corim/testcases/comid-ext.json @@ -29,21 +29,19 @@ "model": "RoadRunner" } }, - "measurements": [ - { - "key": { - "type": "cca.platform-config-id", - "value": "cfg v1.0.0" - }, - "value": { - "timestamp": 1720782190, - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" - } - } - } - ] + "measurement": { + "key": { + "type": "cca.platform-config-id", + "value": "cfg v1.0.0" + }, + "value": { + "timestamp": 1720782190, + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } + } + } } ] } diff --git a/corim/testcases/comid.json b/corim/testcases/comid.json index 974a473b..9c4125bc 100644 --- a/corim/testcases/comid.json +++ b/corim/testcases/comid.json @@ -28,20 +28,18 @@ "model": "RoadRunner" } }, - "measurements": [ - { - "key": { - "type": "cca.platform-config-id", - "value": "cfg v1.0.0" - }, - "value": { - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" - } - } - } - ] + "measurement": { + "key": { + "type": "cca.platform-config-id", + "value": "cfg v1.0.0" + }, + "value": { + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } + } + } } ] } diff --git a/corim/testcases/signed-corim-with-extensions.cbor b/corim/testcases/signed-corim-with-extensions.cbor index 4bfca682959be82d3ace80585e5e20241a5f7759..eb03f4ad7520bb67d733aa12b390beacf7840314 100644 GIT binary patch delta 271 zcmZ3wKo0G}*CgZOqOpQw>o>4Sn zyrEE%QBqP+Y^ATCn4Fucms*jSTac5=vXG&++>PeJeiS6g)7O)hb7fW&rr``auTDS07wt2Y8}Q!llL>4PM*l9H}Rv*pxfusT0AarPSbZ~PzFE3$ZZDlTHcwudDY-N#| zAqWJa0D*#`0HcxYCX+q_5)y-=0NDarqXA+A~BWLQ8DEQn~y6W)ZU!NT-eKje9#78O`Hv*Y4*b=BOCfl2gzm_mHE}3{jX5wmD7M6t! z%@bePFfN)L$*3p5aFZ!=F=LXG4@;_%o}r$>SHfYJ&Iqt<9X`hwY&G{7`wO|{`mXO#%V5}$~$K!1%@J(<@%Dj>W|h* buR9Q$dauOr(ws#CnTjtL31;0{Xd(mvu3T%+ delta 261 zcmZ3?wuW_rVhF<`h7w0-Usna6k`#sF%=Em>ymW=^)Jn!13MCmOB?ZM+`ud5cOp zEn#Y0GI59O#5po7EDISLC*HJSZ(hW3lPPlXLp_7ZVvKsi5E;gl zfS~*kmQ+JMV?9%lvdLQ+b$MWNjzGD|kxbf?yO=zjA{=hFHcHob?RyU_~bLD)iLq6GU%X?b7X~S*{#Z#YlS;+7#F986; CBVkh2qTgyv)3G zh3wSI1(8hpix_fCQj1FzlJko)a}_dE7#ky*q;4|)S~B@Mqnr~i^O%|$7?~I{VTS6l zEM#bITF9`NF$E;Vlogbpm=aW)mzP>Ju}y8_{ulv}B&x8^WNF5@iA^?)izcsU)a1U& z6uFos)j-cs&t$S5ql$ZqV^Drbgu}FTkF%W*NR}H){Vlkp8n7YFVQ;H>ard3b2gf`* pe*bk5na5yk)zQWN%ytLUBbL8rOExWDbwRO6N%bCo@a1D`%mJW(X)XW& delta 216 zcmcb{dY@&2u^2;%qqDE8f=@|`LUCq#US?jpLUwB9#5oeG3nCeJEMmwlNi8l>NX{?H z%vH!tVQh?KTzr%9*AkY642?|-85U12VAPqc#l*wjyoli@Q{>{w>lsxh>oFQlp2HX? z3=v{XaSX~2VM#U6Gt@Hy%4<)yXHuM4FDt;*%)rRRkO{M24```Vgu{B4m)2*C+!gA= z>>dSaM^t*GOT~7nNnc!P_{=-rGPX;2A%CJpFE2})_P&jC|Hkb6#k@Vc??&wJYdaYo J(<}QQ004VXPDB6z diff --git a/corim/testcases/src/corim-with-extensions.yaml b/corim/testcases/src/corim-with-extensions.yaml index 2f1a45f1..0eec372e 100644 --- a/corim/testcases/src/corim-with-extensions.yaml +++ b/corim/testcases/src/corim-with-extensions.yaml @@ -33,43 +33,57 @@ YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= 1: ACME 2: RoadRunner - - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - -1: 1720782190 - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - -1: 1720782190 - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= - -1: 1720782190 + - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 diff --git a/corim/testcases/src/example-corim.yaml b/corim/testcases/src/example-corim.yaml index 55d45847..7dff7941 100644 --- a/corim/testcases/src/example-corim.yaml +++ b/corim/testcases/src/example-corim.yaml @@ -36,43 +36,57 @@ YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= 1: ACME 2: RoadRunner - - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - -1: 1720782190 - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - -1: 1720782190 - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= - -1: 1720782190 + - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 diff --git a/corim/testcases/src/good-corim.yaml b/corim/testcases/src/good-corim.yaml index 4e61fee6..ca12ebe8 100644 --- a/corim/testcases/src/good-corim.yaml +++ b/corim/testcases/src/good-corim.yaml @@ -27,40 +27,54 @@ YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= 1: ACME 2: RoadRunner - - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/corim/testcases/unsigned-corim-with-extensions.cbor b/corim/testcases/unsigned-corim-with-extensions.cbor index 0e056df92a7d13cf5caff8df0858e92e82bb1894..a67d06388ac3d70013dce48a964dbbeb691974f6 100644 GIT binary patch delta 188 zcmZo-`N=XtTaU^1CgZOqj0+h8oOeI2*Gu#^UUxb8{70v5F{Z{P3i*ac#tLqU*{KS_ zB}J*JB@_KMSXdS^G*9fYVO%uvhw;QydMr%~85U2Rqa(_c6_lTt5>%R(ms+%lagl;l z+C<*G$;pgrlXVzPVXEX&R59FSid@W?;uw@4!jfvBXQ*c~*^W__4XX7xqna2)YHFUY VyAwlhNosM4LUMjlX0AeJ3IJyqK~4Yw delta 124 zcmey#(!?@Bo0sv}O~zkK7*bR7blsgM8j4Sh)DTt3H#9OkT%wSiUzC}vkeLDiK6)tA diff --git a/corim/testcases/unsigned-example-corim.cbor b/corim/testcases/unsigned-example-corim.cbor index 0c715c8f24d0886906a5f8df3eb40b02d6b4d4dd..fff8f2e0c1583e05a9b6e623eb85a1cb218b2c7d 100644 GIT binary patch delta 179 zcmZo=`NP7vWbzqCOKrx+NG98xjK7vJE@TLB-u<{QQbWI8(g zx+?gTr07ka%&5o0vXG&9BA?ATxq Date: Thu, 22 Aug 2024 15:33:58 -0400 Subject: [PATCH 050/110] cocli: move cocli to a standalone repo Relocate cocli to a separate repo under Veraison Organization. Preserve git history during the process. cocli will be available at https://github.com/veraison/cocli Signed-off-by: Jagannathan Raman --- .gitignore | 4 - Makefile | 30 +- README.md | 2 - cocli/README.md | 692 +----------------- cocli/cmd/comid.go | 26 - cocli/cmd/comidCreate.go | 132 ---- cocli/cmd/comidCreate_test.go | 128 ---- cocli/cmd/comidDisplay.go | 98 --- cocli/cmd/comidDisplay_test.go | 101 --- cocli/cmd/comidValidate.go | 108 --- cocli/cmd/comidValidate_test.go | 116 --- cocli/cmd/common.go | 92 --- cocli/cmd/corim.go | 26 - cocli/cmd/corimCreate.go | 238 ------ cocli/cmd/corimCreate_test.go | 266 ------- cocli/cmd/corimDisplay.go | 132 ---- cocli/cmd/corimDisplay_test.go | 146 ---- cocli/cmd/corimExtract.go | 129 ---- cocli/cmd/corimExtract_test.go | 140 ---- cocli/cmd/corimSign.go | 153 ---- cocli/cmd/corimSign_test.go | 264 ------- cocli/cmd/corimSubmit.go | 143 ---- cocli/cmd/corimSubmit_test.go | 174 ----- cocli/cmd/corimVerify.go | 103 --- cocli/cmd/corimVerify_test.go | 133 ---- cocli/cmd/cots.go | 26 - cocli/cmd/cotsCreate.go | 281 ------- cocli/cmd/cotsCreate_test.go | 146 ---- cocli/cmd/cotsDisplay.go | 96 --- cocli/cmd/cotsDisplay_test.go | 101 --- cocli/cmd/isubmitter.go | 19 - cocli/cmd/root.go | 114 --- cocli/cmd/test_vars.go | 66 -- cocli/cmd/testcases/ec-p256.jwk | 9 - cocli/cmd/testcases/psa-refval-orig.cbor | Bin 416 -> 0 bytes cocli/cmd/testcases/psa-refval.cbor | Bin 538 -> 0 bytes cocli/cmd/testcases/regen-from-src.sh | 40 - cocli/cmd/testcases/signed-corim-invalid.cbor | Bin 166 -> 0 bytes .../signed-corim-valid-with-cots.cbor | Bin 2349 -> 0 bytes cocli/cmd/testcases/signed-corim-valid.cbor | Bin 918 -> 0 bytes cocli/cmd/testcases/src/psa-refval.yaml | 76 -- .../src/signed-corim-invalid-meta.yaml | 13 - .../testcases/src/signed-corim-invalid.yaml | 1 - .../src/signed-corim-valid-meta.yaml | 13 - .../signed-corim-valid-with-cots-meta.yaml | 3 - .../src/signed-corim-valid-with-cots.yaml | 6 - .../cmd/testcases/src/signed-corim-valid.yaml | 99 --- cocli/cmd/testcases/src/test-comid.yaml | 76 -- cocli/cmd/testcases/src/test-coswid.yaml | 35 - cocli/cmd/testcases/src/test-cots.yaml | 17 - cocli/cmd/testcases/test-comid.cbor | Bin 538 -> 0 bytes cocli/cmd/testcases/test-coswid.cbor | Bin 377 -> 0 bytes cocli/cmd/testcases/test-cots.cbor | Bin 1943 -> 0 bytes cocli/data/comid/1.cbor | Bin 531 -> 0 bytes cocli/data/comid/2.cbor | Bin 416 -> 0 bytes cocli/data/comid/comid-cca-mult-refval.cbor | Bin 457 -> 0 bytes cocli/data/comid/comid-cca-refval.cbor | Bin 177 -> 0 bytes cocli/data/comid/comid-dice-refval.cbor | Bin 220 -> 0 bytes cocli/data/comid/comid-psa-iakpub.cbor | Bin 637 -> 0 bytes cocli/data/comid/comid-psa-integ-iakpub.cbor | Bin 638 -> 0 bytes cocli/data/comid/comid-psa-refval.cbor | Bin 416 -> 0 bytes .../templates/comid-cca-mult-refval.json | 94 --- .../templates/comid-cca-realm-refval.json | 75 -- .../comid/templates/comid-cca-refval.json | 49 -- .../comid/templates/comid-dice-refval.json | 70 -- .../comid/templates/comid-psa-iakpub.json | 66 -- .../templates/comid-psa-integ-iakpub.json | 66 -- .../comid/templates/comid-psa-refval.json | 81 -- cocli/data/config/example-config.yaml | 24 - cocli/data/corim/corim-full.cbor | Bin 1495 -> 0 bytes .../corim/signed-corim-bad-signature.cbor | Bin 922 -> 0 bytes cocli/data/corim/signed-corim-cbor | Bin 1622 -> 0 bytes cocli/data/corim/signed-corim.cbor | Bin 603 -> 0 bytes .../data/corim/templates/corim-cca-realm.json | 17 - cocli/data/corim/templates/corim-cca.json | 17 - cocli/data/corim/templates/corim-full.json | 23 - cocli/data/corim/templates/corim-mini.json | 3 - cocli/data/corim/templates/meta-full.json | 10 - cocli/data/corim/templates/meta-mini.json | 5 - cocli/data/corim/unsigned-corim.cbor | Bin 815 -> 0 bytes cocli/data/coswid/1.cbor | Bin 377 -> 0 bytes cocli/data/cots/Zesty Hands_ta.ta | Bin 698 -> 0 bytes cocli/data/cots/cts-map.cbor | Bin 2195 -> 0 bytes cocli/data/cots/namedtastore.cbor | Bin 766 -> 0 bytes cocli/data/cots/rubbish.cbor | Bin 1078 -> 0 bytes .../data/cots/templates/claims/exclclaim.json | 1 - .../data/cots/templates/claims/permclaim.json | 1 - cocli/data/cots/templates/env/comid.json | 1 - cocli/data/cots/templates/env/coswid.json | 1 - .../data/cots/templates/env/namedtastore.json | 1 - cocli/data/cots/templates/env/vendor.json | 1 - cocli/data/cots/templates/env/vendor2.json | 1 - cocli/data/cots/templates/env/vendors.json | 1 - cocli/data/cots/vendor.cbor | Bin 674 -> 0 bytes cocli/data/cots/worthlesssea.spki | Bin 91 -> 0 bytes cocli/data/keys/ec-p256.jwk | 9 - cocli/data/pics/cocli-map.graffle | Bin 171894 -> 0 bytes cocli/data/pics/cocli-map.png | Bin 110025 -> 0 bytes cocli/main.go | 12 - .../cots => cots/data}/Snobbish Apparel_ta.ta | Bin {cocli/data/cots => cots/data}/shared_ta.ta | Bin cots/example_test.go | 4 +- scripts/licenses.sh | 1 - 103 files changed, 9 insertions(+), 5438 deletions(-) delete mode 100644 cocli/cmd/comid.go delete mode 100644 cocli/cmd/comidCreate.go delete mode 100644 cocli/cmd/comidCreate_test.go delete mode 100644 cocli/cmd/comidDisplay.go delete mode 100644 cocli/cmd/comidDisplay_test.go delete mode 100644 cocli/cmd/comidValidate.go delete mode 100644 cocli/cmd/comidValidate_test.go delete mode 100644 cocli/cmd/common.go delete mode 100644 cocli/cmd/corim.go delete mode 100644 cocli/cmd/corimCreate.go delete mode 100644 cocli/cmd/corimCreate_test.go delete mode 100644 cocli/cmd/corimDisplay.go delete mode 100644 cocli/cmd/corimDisplay_test.go delete mode 100644 cocli/cmd/corimExtract.go delete mode 100644 cocli/cmd/corimExtract_test.go delete mode 100644 cocli/cmd/corimSign.go delete mode 100644 cocli/cmd/corimSign_test.go delete mode 100644 cocli/cmd/corimSubmit.go delete mode 100644 cocli/cmd/corimSubmit_test.go delete mode 100644 cocli/cmd/corimVerify.go delete mode 100644 cocli/cmd/corimVerify_test.go delete mode 100644 cocli/cmd/cots.go delete mode 100644 cocli/cmd/cotsCreate.go delete mode 100644 cocli/cmd/cotsCreate_test.go delete mode 100644 cocli/cmd/cotsDisplay.go delete mode 100644 cocli/cmd/cotsDisplay_test.go delete mode 100644 cocli/cmd/isubmitter.go delete mode 100644 cocli/cmd/root.go delete mode 100644 cocli/cmd/test_vars.go delete mode 100644 cocli/cmd/testcases/ec-p256.jwk delete mode 100644 cocli/cmd/testcases/psa-refval-orig.cbor delete mode 100644 cocli/cmd/testcases/psa-refval.cbor delete mode 100644 cocli/cmd/testcases/regen-from-src.sh delete mode 100644 cocli/cmd/testcases/signed-corim-invalid.cbor delete mode 100644 cocli/cmd/testcases/signed-corim-valid-with-cots.cbor delete mode 100644 cocli/cmd/testcases/signed-corim-valid.cbor delete mode 100644 cocli/cmd/testcases/src/psa-refval.yaml delete mode 100644 cocli/cmd/testcases/src/signed-corim-invalid-meta.yaml delete mode 100644 cocli/cmd/testcases/src/signed-corim-invalid.yaml delete mode 100644 cocli/cmd/testcases/src/signed-corim-valid-meta.yaml delete mode 100644 cocli/cmd/testcases/src/signed-corim-valid-with-cots-meta.yaml delete mode 100644 cocli/cmd/testcases/src/signed-corim-valid-with-cots.yaml delete mode 100644 cocli/cmd/testcases/src/signed-corim-valid.yaml delete mode 100644 cocli/cmd/testcases/src/test-comid.yaml delete mode 100644 cocli/cmd/testcases/src/test-coswid.yaml delete mode 100644 cocli/cmd/testcases/src/test-cots.yaml delete mode 100644 cocli/cmd/testcases/test-comid.cbor delete mode 100644 cocli/cmd/testcases/test-coswid.cbor delete mode 100644 cocli/cmd/testcases/test-cots.cbor delete mode 100644 cocli/data/comid/1.cbor delete mode 100644 cocli/data/comid/2.cbor delete mode 100644 cocli/data/comid/comid-cca-mult-refval.cbor delete mode 100644 cocli/data/comid/comid-cca-refval.cbor delete mode 100644 cocli/data/comid/comid-dice-refval.cbor delete mode 100644 cocli/data/comid/comid-psa-iakpub.cbor delete mode 100644 cocli/data/comid/comid-psa-integ-iakpub.cbor delete mode 100644 cocli/data/comid/comid-psa-refval.cbor delete mode 100644 cocli/data/comid/templates/comid-cca-mult-refval.json delete mode 100644 cocli/data/comid/templates/comid-cca-realm-refval.json delete mode 100644 cocli/data/comid/templates/comid-cca-refval.json delete mode 100644 cocli/data/comid/templates/comid-dice-refval.json delete mode 100644 cocli/data/comid/templates/comid-psa-iakpub.json delete mode 100644 cocli/data/comid/templates/comid-psa-integ-iakpub.json delete mode 100644 cocli/data/comid/templates/comid-psa-refval.json delete mode 100644 cocli/data/config/example-config.yaml delete mode 100644 cocli/data/corim/corim-full.cbor delete mode 100644 cocli/data/corim/signed-corim-bad-signature.cbor delete mode 100644 cocli/data/corim/signed-corim-cbor delete mode 100644 cocli/data/corim/signed-corim.cbor delete mode 100644 cocli/data/corim/templates/corim-cca-realm.json delete mode 100644 cocli/data/corim/templates/corim-cca.json delete mode 100644 cocli/data/corim/templates/corim-full.json delete mode 100644 cocli/data/corim/templates/corim-mini.json delete mode 100644 cocli/data/corim/templates/meta-full.json delete mode 100644 cocli/data/corim/templates/meta-mini.json delete mode 100644 cocli/data/corim/unsigned-corim.cbor delete mode 100644 cocli/data/coswid/1.cbor delete mode 100644 cocli/data/cots/Zesty Hands_ta.ta delete mode 100644 cocli/data/cots/cts-map.cbor delete mode 100644 cocli/data/cots/namedtastore.cbor delete mode 100644 cocli/data/cots/rubbish.cbor delete mode 100644 cocli/data/cots/templates/claims/exclclaim.json delete mode 100644 cocli/data/cots/templates/claims/permclaim.json delete mode 100644 cocli/data/cots/templates/env/comid.json delete mode 100644 cocli/data/cots/templates/env/coswid.json delete mode 100644 cocli/data/cots/templates/env/namedtastore.json delete mode 100644 cocli/data/cots/templates/env/vendor.json delete mode 100644 cocli/data/cots/templates/env/vendor2.json delete mode 100644 cocli/data/cots/templates/env/vendors.json delete mode 100644 cocli/data/cots/vendor.cbor delete mode 100644 cocli/data/cots/worthlesssea.spki delete mode 100644 cocli/data/keys/ec-p256.jwk delete mode 100644 cocli/data/pics/cocli-map.graffle delete mode 100644 cocli/data/pics/cocli-map.png delete mode 100644 cocli/main.go rename {cocli/data/cots => cots/data}/Snobbish Apparel_ta.ta (100%) rename {cocli/data/cots => cots/data}/shared_ta.ta (100%) diff --git a/.gitignore b/.gitignore index 6bb26a80..06b53509 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,3 @@ # vendor/ mocks/ - -# Generated during testing -cocli/cocli -cocli/cmd/output.cbor diff --git a/Makefile b/Makefile index f5e8bbdb..a22b3fd4 100644 --- a/Makefile +++ b/Makefile @@ -7,14 +7,9 @@ GO111MODULE := on GOPKG := github.com/veraison/corim/corim GOPKG += github.com/veraison/corim/comid GOPKG += github.com/veraison/corim/cots -GOPKG += github.com/veraison/corim/cocli/cmd GOPKG += github.com/veraison/corim/encoding GOPKG += github.com/veraison/corim/extensions -MOCKGEN := $(shell go env GOPATH)/bin/mockgen -INTERFACES := cocli/cmd/isubmitter.go -MOCKPKG := mocks - GOLINT ?= golangci-lint ifeq ($(MAKECMDGOALS),lint) @@ -26,7 +21,8 @@ else endif .PHONY: lint lint-extra -lint lint-extra: _mocks; $(GOLINT) $(GOLINT_ARGS) +lint lint-extra: + $(GOLINT) $(GOLINT_ARGS) ifeq ($(MAKECMDGOALS),test) GOTEST_ARGS ?= -v -race $(GOPKG) @@ -38,28 +34,14 @@ endif COVER_THRESHOLD := $(shell grep '^name: cover' .github/workflows/ci-go-cover.yml | cut -c13-) - -define MOCK_template -mock_$(1): $(1) - $$(MOCKGEN) -source=$$< -destination=cocli/cmd/mocks/$$$$(basename $$@) -package=$$(MOCKPKG) -endef - -$(foreach m,$(INTERFACES),$(eval $(call MOCK_template,$(m)))) -MOCK_FILES := $(foreach m,$(INTERFACES),$(join mock_,$(m))) -CLEANFILES := $(MOCK_FILES) - -_mocks: $(MOCK_FILES) -.PHONY: _mocks - .PHONY: test test-cover -test test-cover: _mocks; go test $(GOTEST_ARGS) +test test-cover: + go test $(GOTEST_ARGS) -realtest: _mocks; go test $(GOTEST_ARGS) +realtest: + go test $(GOTEST_ARGS) .PHONY: realtest -.PHONY: clean -clean: ; $(RM) $(CLEANFILES) - presubmit: @echo @echo ">>> Check that the reported coverage figures are $(COVER_THRESHOLD)" diff --git a/README.md b/README.md index 8dd87998..36ff7bd6 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,6 @@ The [`corim/corim`](corim) and [`corim/comid`](comid) packages provide a golang > These API are still in active development (as is the underlying CoRIM spec). > They are **subject to change** in the future. -The [`corim/cocli`](cocli) package uses the API above (as well as the API from [`veraison/swid`](https://github.com/veraison/swid) package) to provide a user friendly command line interface for working with CoRIM, CoMID, CoSWID and CoTS. Specifically it allows creating, signing, verifying, displaying, uploading, and more. See [`cocli/README.md`](cocli/README.md) for further details. - ## Developer tips Before requesting a PR (and routinely during the dev/test cycle), you are encouraged to run: diff --git a/cocli/README.md b/cocli/README.md index 7d0e3969..338ec40a 100644 --- a/cocli/README.md +++ b/cocli/README.md @@ -1,693 +1,3 @@ # Corim Command Line Interface -In this document we describe how to use Corim Command Line Interface tool `cocli` - -* [Installation](#installing-and-configuring) -* [Command Snapshot](#cocli-command-snapshot) -* [Supported Commands](#supported-commands) - * [CoMID Commands](#comids-manipulation) - * [Create](#create) - * [Display](#display) - * [CoTS Commands](#cotss-manipulation) - * [Create](#create-1) - * [Display](#display-1) - * [CoRIM Commands](#corims-manipulation) - * [Create](#create-2) - * [Sign](#sign) - * [Verify](#verify) - * [Display](#display-2) - * [Extract](#extract-coswids-comids-and-cotss) - * [CoRIM Submission](#corim-submission-to-veraison) - * [Remote Authentication](#remote-service-authentication) - * [Command Synopsis](#visual-synopsis-of-the-available-commands) - -# Installing and configuring - -To install the `cocli` command, do: -``` -$ go install github.com/veraison/corim/cocli@latest -``` - -To configure auto-completion, use the `completion` subcommand. For example, if -`bash` is your shell, you would do something like: -``` -$ cocli completion bash > ~/.bash_completion.d/cocli -$ . ~/.bash_completion -``` -to get automatic command completion and suggestions using the TAB key. - -To get a list of the supported shells, do: -``` -$ cocli completion --help -``` -# Cocli Command Snapshot -This document provides step-by-step instructions for how to use the `cocli` tool to manipulate CoRIMs, CoMIDs and CoTS. - -```mermaid -flowchart TD - subgraph COCLI["COCLI COMMANDS"] - style COCLI fill:#ffffff, stroke:#333,stroke-width:4px - subgraph CORIMCMD["CORIM COMMANDS \n - cocli corim create \n cocli corim display \n cocli corim sign \n cocli corim verify\n cocli corim extract\n cocli corim submit"] - end - subgraph COMIDCMD["COMID COMMANDS \n cocli comid create \n cocli comid display"] - end - - subgraph COTSCMD["COTS COMMANDS \n cocli cots create \n cocli cots display"] - end - end - CORIM ---> CORIMCMD -subgraph CORIM["CoRIM"] - subgraph CoMID["COMIDs\n"] - CM3["CoMID-N"] - CM2["CoMID-2"] - CM1["CoMID-1"] - CM1 -.- CM2 - CM2 -.- CM3 - CM3 ---> COMIDCMD - end - - subgraph CoSWID["CoSWID\n"] - CSW1["CoSWID-1"] - CSW2["CoSWID-2"] - CSW3["CoSWID-N"] - CSW1 -.- CSW2 - CSW2 -.- CSW3 - end - - subgraph CoTS["COTS\n"] - CS3["CoTS-N"] - CS2["CoTS-2"] - CS1["CoTS-1"] - CS1 -.- CS2 - CS2 -.- CS3 - CS3 ---> COTSCMD - end - -end - -``` - -# Supported Commands -This section describes all the available commands supported by `cocli` tool - -## CoMIDs manipulation - -The `comid` subcommand allows you to create, display and validate CoMIDs. - -### Create - -Use the `comid create` subcommand to create a CBOR-encoded CoMID, passing its -JSON representation via the `--template` switch (or equivalently its `-t` shorthand): - -* Please inspect `comid` JSON templates as examples under `data/comid/templates` `comid-*.json` - -``` -$ cocli comid create --template data/comid/templates/comid-dice-refval.json -``` -On success, you should see something like the following printed to stdout: -``` ->> created "comid-dice-refval.cbor" from "comid-dice-refval.json" -``` - -The CBOR-encoded CoMID file is stored in the current working directory with a -name derived from its template. If you want, you can specify a different -target directory using the `--output-dir` command line switch (abbrev. `-o`) -``` -$ cocli comid create --template data/comid/templates/comid-dice-refval.json --output-dir /tmp ->> created "/tmp/comid-dice-refval.cbor" from "comid-dice-refval.json" -``` -Note that the output directory, as well as all its parent directories, MUST -pre-exist. - -You can also create multiple CoMIDs in one go. Suppose all your templates are -stored in the `templates/` folder: -``` -$ tree templates/ -templates/ -├── comid-dice-refval1.json -├── comid-dice-refval2.json -... -└── comid-dice-refvaln.json -``` -Then, you can use the `--template-dir` (abbrev. `-T`), and let the tool load, -validate, and CBOR-encode the templates one by one: -``` -$ cocli comid create --template-dir templates ->> created "comid-dice-refval1.cbor" from "templates/comid-dice-refval1.json" ->> created "comid-dice-refval2.cbor" from "templates/comid-dice-refval2.json" -... ->> created "comid-dice-refvaln.cbor" from "templates/comid-dice-refvaln.json" -``` - -You can specify both the `-T` and `-t` switches as many times as needed, and -even combine them in one invocation: -``` -$ cocli comid create -T comid-templates/ \ - -T comid-templates-aux/ \ - -t extra-comid.json \ - -t yet-another-comid.json \ - -o /var/spool/comid -``` - -**NOTE** that since the output file name is deterministically generated from the -template file name, all the template files (when from different directories) -MUST have different base names. - - -### Display - -Use the `comid display` subcommand to print to stdout one or more CBOR-encoded -CoMIDs in human readable (JSON) format. - -You can supply individual files using the `--file` switch (abbrev. `-f`), or -directories that may (or may not) contain CoMID files using the `--dir` switch -(abbrev. `-d`). Only valid CoMIDs will be displayed, and any decoding or -validation error will be printed alongside the corresponding file name. - -For example: -``` -$ cocli comid display --file data/comid/comid-dice-refval.cbor -``` -provided the `comid-dice-refval.cbor` file contains valid CoMID, would print something like: -``` ->> [comid-dice-refval.cbor] -{ - "tag-identity": { - "id": "1d5a8c7c-1c70-4c56-937e-3c5713ae5a83" - }, - "triples": {} -[...] -} -``` -While a `data/comid/` folder with the following contents: -``` -$ tree data/comid/ -data/comid/ -├── rubbish.cbor -├── 1.cbor -└── 2.cbor -``` -could be inspected in one go using: -``` -$ cocli comid display --dir data/comid/ -``` -which would output something like: -``` ->> failed displaying "comids.d/rubbish.cbor": CBOR decoding failed: EOF ->> [data/comid/1.cbor] -{ - "tag-identity": { - "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" - }, -[...] -} ->> [data/comid/2.cbor] -{ - "tag-identity": { - "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" - }, -[...] -} -Error: 1/3 display(s) failed -``` - -One or more files and directories can be supplied in the same invocation, e.g.: -``` -$ cocli comid display -f m1.cbor \ - -f comids.d/m2.cbor \ - -d /var/spool/comids \ - -d yet-another-comid-folder/ -``` - -## CoTSs manipulation -The `cots` subcommand allows you to create, display and validate CoTSs. - -### Create - -Use the `cots create` subcommand to create a CBOR-encoded CoTS. The `environment` switch takes in a JSON template specifiying the environments that are valid for the keys specified and the `tas` switch takes in a directory of trust anchors files: - -* Please inspect `data/cots/templates` JSON templates as examples for `environment` and `claims` - - -``` -$ cocli cots create --environment data/cots/env/vendor.json --tafile data/cots/shared_ta.ta -``` -On success, you should see something like the following printed to stdout: -``` ->> created "vendor.cbor" -``` - -The CBOR-encoded CoTS file is stored in the current working directory with a -name derived from its environment template. If you want, you can specify a different -target directory and file name using the `--output` command line switch (abbrev. `-o`) -``` -$ cocli cots create --environment data/cots/env/vendor.json --tafile data/cots/shared_ta.ta --output /tmp/myCots.cbor ->> created "/tmp/myCots.cbor" -``` -Note that the output directory, as well as all its parent directories, MUST pre-exist. - -### Display - -Use the `cots display` subcommand to print to stdout one or more CBOR-encoded -CoTSs in human readable (JSON) format. - -You can supply individual files using the `--file` switch (abbrev. `-f`), or -directories that may (or may not) contain CoTS files using the `--dir` switch -(abbrev. `-d`). Only valid CoTSs will be displayed, and any decoding or -validation error will be printed alongside the corresponding file name. - -For example: -``` -$ cocli cots display --file vendor.cbor -``` -provided the `vendor.cbor` file contains valid CoTS, would print something like: -``` ->> [vendor.cbor] -{ - "environments": [ - { - "environment": { - "class": { - "vendor": "Zesty Hands, Inc." - } - } - } - ], - "keys": { - "tas": [ - { - "format": 1, - "data": "ooICejCCAnYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATjUaoQOSQHpL0DfKC8EVTQ5wHwZ085yyxPkhBpLOu+7B0nl33FYWV1Hg4je/37FTbpmohFkUKWYd81z8C/K1DMBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzCCAgEwPjELMAkGA1UEBgwCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHTAbBgNVBAMMFEV4YW1wbGUgVHJ1c3QgQW5jaG9yoIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M=" - } - ] - } -} - -``` -While a `data/cots` folder with the following contents: -``` -$ tree cots/ -cots/ -├── rubbish.cbor -├── namedtastore.cbor -├── vendor.cbor -``` -could be inspected in one go using: -``` -$ cocli cots display --dir data/cots/ -``` -which would output something like: -``` ->> [data/cots/namedtastore.cbor] -{ - "environments": [ - { - "namedtastore": "Miscellaneous TA Store" - } - ], - "keys": { - "tas": [ - { - "format": 1, - "data": "ooIC1TCCAtEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN0f5kzywEzZOYbaV23O3N8cku39JoLNjlHPwECbXDDWp0LpAO1z248/hoy6UW/TZMTPPR/93XwHsG16mSFy8XBBSKhM/5gJWjvDbW7qUY1peNm9cfYDCCAlwwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yoIIB+jCCAZ+gAwIBAgIUEBuTRGXAEEVEHhu4xafAnqm+qYgwCgYIKoZIzj0EAwIwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMB4XDTIyMDUxOTE1MTMwOFoXDTMyMDUxNjE1MTMwOFowXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzdH+ZM8sBM2TmG2ldtztzfHJLt/SaCzY5Rz8BAm1ww1qdC6QDtc9uPP4aMulFv02TEzz0f/d18B7BtepkhcvF6M/MD0wHQYDVR0OBBYEFIqEz/mAlaO8NtbupRjWl42b1x9gMAsGA1UdDwQEAwIChDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQC2cf43f3PPlCO6/dxv40ftIgxxToKHF72UzENv7+y4ygIhAIGtC/r6SGaFMaP7zD2EloBuIXTtyWu8Hwl+YGdXRY93" - } - ] - } -} ->> failed displaying "data/cots/rubbish.cbor": CBOR decoding failed: cbor: cannot unmarshal primitives into Go value of type cots.ConciseTaStore ->> [data/cots/vendor.cbor] -{ - "environments": [ - { - "environment": { - "class": { - "vendor": "Zesty Hands, Inc." - } - } - } - ], - "keys": { - "tas": [ - { - "format": 1, - "data": "ooICejCCAnYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATjUaoQOSQHpL0DfKC8EVTQ5wHwZ085yyxPkhBpLOu+7B0nl33FYWV1Hg4je/37FTbpmohFkUKWYd81z8C/K1DMBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzCCAgEwPjELMAkGA1UEBgwCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHTAbBgNVBAMMFEV4YW1wbGUgVHJ1c3QgQW5jaG9yoIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M=" - } - ] - } -} - -Note: One of more files and directories can be supplied in the same invocation, using -f and -d directive: - -``` - -## CoSWID manipulation - -Tooling to manipulate `CoSWID` is not currently available under Project Veraison. -However CoSWID can be part of CoRIM by constructing CoSWID CBOR by other indistry available -tools such as [swid-tools](https://github.com/usnistgov/swid-tools) and including them -as mentioned under [CORIM Construction](CORIM.md) - -## CoRIMs manipulation - -The `corim` subcommand allows you to create, display, sign, verify CoRIMs or submit -a CoRIM using the [Veraison provisioning API](https://github.com/veraison/docs/tree/main/api/endorsement-provisioning). -It also provides a means to extract as-is the embedded CoSWIDs, CoMIDs and CoTSs and save -them as separate files. - -### Create - -Use the `corim create` subcommand to create a CBOR-encoded, unsigned CoRIM, by -passing its JSON representation via the `--template` switch (or equivalently its `-t` shorthand) -together with the CBOR-encoded CoMIDs, CoSWIDs and/or CoTS to be embedded. - -* Please inspect `corim` JSON templates as examples under `data/corim/templates` `corim-*.json` - -``` -$ cocli corim create --template data/corim/templates/corim-full.json --comid data/comid/comid-dice-refval.cbor --coswid data/coswid/1.cbor --cots data/cots/vendor.cbor -``` -On success, you should see something like the following printed to stdout: -``` ->> created "corim-full.cbor" from "corim-full.json" -``` - -The CBOR-encoded CoRIM file is stored in the current working directory with a -name derived from its template. If you want, you can specify a different -file name using the `--output` command line switch (abbrev. `-o`): -``` -$ cocli corim create -t data/corim/templates/corim-full.json -m data/comid/comid-dice-refval.cbor -s data/coswid/1.cbor -c data/cots/c1.cbor -o unsigned-corim.cbor ->> created "unsigned-corim.cbor" from "corim-full.json" -``` - -CoMIDs, CoSWIDs and CoTSs can be either supplied as individual files, using the -`--comid` (abbrev. `-m`), `--coswid` (abbrev. `-s`) and `--cots` (abbrev. `-c`) switches respectively, or -as "per-folder" blocks using the `--comid-dir` (abbrev. `-M`), `--coswid-dir` and `--cots-dir` -(abbrev. `-C`) switch. For example: -``` -$ cocli corim create --template data/corim/templates/corim-full.json --comid-dir data/comid/cbor/ -``` - -Creation will fail if *any* of the inputs is non conformant. For example, if -`data/comid/cbor/` contains an invalid CoMID file `rubbish.cbor`, an attempt to create a -CoRIM: -``` -$ cocli corim create -t data/corim/templates/corim-full.json -M data/comid/cbor/ -``` -will fail with: -``` -Error: error loading CoMID from data/comid/cbor/rubbish.cbor: EOF -``` - -### Sign - -Use the `corim sign` subcommand to cryptographically seal the unsigned CoRIM -supplied via the `--file` switch (abbrev. `-f`). The signature is produced -using the key supplied via the `--key` switch (abbrev. `-k`), which is expected -to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. On success, the -resulting COSE Sign1 payload is saved to file whose name can be controlled using -the `--output` switch (abbrev. `-o`). A CoRIM Meta template in JSON format must -also be provided using the `--meta` switch (abbrev.`-m`). - -* Please inspect the `data/corim/templates` directory for `meta` JSON templates. - -For example, with the default output file: -``` -$ cocli corim sign --file corim.cbor --key ec-p256.jwk --meta meta.json ->> "corim.cbor" signed and saved to "signed-corim.cbor" -``` -Or, the same but with a custom output file: -``` -$ cocli corim sign --file data/corim/corim-full.cbor \ - --key data/keys/ec-p256.jwk \ - --meta data/corim/templates/meta-full.json \ - --output /var/spool/signed-corim.cbor ->> "corim-full.cbor" signed and saved to "/var/spool/signed-corim.cbor" -``` - -### Verify - -Use the `corim verify` subcommand to cryptographically verify the signed CoRIM -supplied via the `--file` switch (abbrev. `-f`). The signature is checked -using the key supplied via the `--key` switch (abbrev. `-k`), which is expected -to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. For example: -``` -$ cocli corim verify --file data/corim/signed-corim.cbor --key data/keys/ec-p256.jwk ->> "signed-corim.cbor" verified -``` - -Verification can fail either because the cryptographic processing fails or -because the signed payload or protected headers are themselves invalid. For example: -``` -$ cocli corim verify --file data/corim/signed-corim-bad-signature.cbor --key data/keys/ec-p256.jwk -``` -will give -``` -Error: error verifying signed-corim-bad-signature.cbor with key ec-p256.jwk: verification failed ecdsa.Verify -``` - -### Display - -Use the `corim display` subcommand to print to stdout a signed CoRIM in human -readable (JSON) format. - -You must supply the file you want to display using the `--file` switch (abbrev. -`-f`). Only a valid CoRIM will be displayed, and any occurring decoding or -validation errors will be printed instead. - -The output has two logical sections: one for Meta and one for the (unsigned) -CoRIM: -``` -$ cocli corim display --file data/corim/signed-corim.cbor -Meta: -{ - "signer": { - "name": "ACME Ltd signing key", - "uri": "https://acme.example/signing-key.pub" - }, -[...] -} -Corim: -{ - "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", - "tags": [ - "2QH...", -[...] - ] -} -``` - -By default, the embedded CoMID, CoSWID and CoTS tags are not expanded, and what you -will see is the base64 encoding of their CBOR serialisation. If you want to -peek at the tags' content, supply the `--show-tags` (abbrev. `-v`) switch, which -will add a further Tags section with one entry per each expanded tag: -``` -$ cocli corim display --file data/corim/signed-corim.cbor --show-tags -Meta: -{ -[...] -} -Corim: -{ -[...] -} -Tags: ->> [ 0 ] -{ - "tag-identity": { - "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" - }, -[...] -} ->> [ 1 ] -{ - "tag-identity": { - "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" - }, -[...] -} ->> [ 2 ] -{ - "tag-id": "com.acme.rrd2013-ce-sp1-v4-1-5-0", -[...] -} -``` - -### Extract CoSWIDs, CoMIDs and CoTSs - -Use the `corim extract` subcommand to extract the embedded CoMIDs, CoSWIDs and CoTSs -from a signed CoRIM. - -You must supply a signed CoRIM file using the `--file` switch (abbrev. `-f`) and -an optional output folder (default is the current working directory) using the -`--output-dir` switch (abbrev. `-o`). Make sure that the output directory as -well as any parent folder exists prior to issuing the command. - -On success, the found CoMIDs, CoSWIDs, CoTS are saved in CBOR format: -``` -$ cocli corim extract --file data/corim/signed-corim.cbor --output-dir output.d/ -$ tree output.d/ -output.d/ -├── 000000-comid.cbor -├── 000001-comid.cbor -├── 000002-coswid.cbor -└── 000003-cots.cbor -``` - -## CoRIM Submission to Veraison - -Use the `corim submit` subcommand to upload a CoRIM using the Veraison provisioning API. -The CoRIM file containing the CoRIM data in CBOR format is supplied via the -`--corim-file` switch (abbrev. `-f`). The server URL where to upload the CoRIM -payload is supplied via the `--api-server` switch (abbrev. `-s`). -Further, it is required to supply the media type of the content via the -`--media-type` switch (abbrev. `-m`) -``` -$ cocli corim submit \ - --corim-file data/corim/unsigned-corim.cbor \ - --api-server "https://veraison.example/endorsement-provisioning/v1/submit" \ - --media-type "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" - ->> "unsigned-corim.cbor" submit ok -``` - -#### Remote Service Authentication - -The above will work if the remote service does not authenticate -endorsement-provisioning API calls. If the service does authenticate, then -cocli must be configured appropriately. This can be done using a `config.yaml` -file located in the current working directory, or in the standard config -path (usually `~/.config/cocli/config.yaml` on XDG-compliant systems). Please -see `./data/config/example-config.yaml` file for details of the configuration -that needs to be provided. - -#### Note on TLS - -If the scheme in the API server URL is HTTPS, `cocli` will attempt to establish -a TLS connection to the server, validating the server certificate using system CA -certs. It is possible to disable server certificate validation with -`-i`/`--insecure` flag. Alternatively, if the CA cert for the server is -available but is not installed in the system, it may be specified using -`-E`/`--ca-cert` flag. - -## Visual Synopsis of the Available Commands - -```mermaid -graph LR - OEM[(OEM/ODM \n DB)] - JSONTmplCoMID[["JSON \n template \n (CoMID)"]] - - JSONTmplCoSWID[["JSON \n template \n (CoSWID)"]] - style JSONTmplCoSWID fill:#71797E - click JSONTmplCoSWID "https://github.com/veraison/corim/issues/81" - - JSONTmplCoRIM[["JSON \n template \n (CoRIM)"]] - JSONTmplMeta[["JSON \n template \n (Meta)"]] - key((key)) - - %% Cots nodes - environments[["Environments"]] - tas(("Trust \n anchors")) - cas(("CA \n certificates")) - permClaims[["Permanant claims"]] - exclClaims[["Excluded claims"]] - - cliComidCreate($ cocli comid create) - cliComidDisplay($ cocli comid display) - style cliComidCreate fill:#00758f - style cliComidDisplay fill:#00758f - - cliCotsCreate($ cocli cots create) - cliCotsDisplay($ cocli cots display) - style cliCotsCreate fill:#00758f - style cliCotsDisplay fill:#00758f - - cliCoswidCreate($ cocli coswid create) - cliCoswidDisplay($ cocli coswid display) - style cliCoswidCreate fill:#71797E - style cliCoswidDisplay fill:#71797E - - - cliCorimCreate($ cocli corim create) - cliCorimSign($ cocli corim sign) - cliCorimVerify($ cocli corim verify) - cliCorimExtract($ cocli corim extract) - cliCorimDisplay($ cocli corim display) - cliCorimSubmit($ cocli corim submit) - style cliCorimCreate fill:#00758f - style cliCorimSign fill:#00758f - style cliCorimVerify fill:#00758f - style cliCorimExtract fill:#00758f - style cliCorimDisplay fill:#00758f - style cliCorimSubmit fill:#00758f - - provisioningEndpoint{{Veraison \n Provisioning \n Service}} - - CBORComid1((CBOR
CoMID)) - CBORSwid1((CBOR
SWID)) - CBORCots1((CBOR
CoTS)) - - CBORComid2((CBOR
CoMID)) - CBORSwid2((CBOR
SWID)) - CBORCots2((CBOR
CoTS)) - - CBORCorim((CBOR CoRIM)) - CoseSign1((COSE Sign1 CoRIM)) - signBool((T/F)) - - OEM --> JSONTmplCoMID - OEM --> JSONTmplCoSWID - - %% Cots items provisioning - OEM --> environments - OEM --> tas - OEM --> cas - OEM --> permClaims - OEM --> exclClaims - - OEM --> JSONTmplCoRIM - OEM --> JSONTmplMeta - OEM --> key - - %% Cots individual items - environments --> cliCotsCreate - tas --> cliCotsCreate - cas --> cliCotsCreate - permClaims --> cliCotsCreate - exclClaims --> cliCotsCreate - - - JSONTmplCoMID --> cliComidCreate - JSONTmplCoSWID --> cliCoswidCreate - JSONTmplCoRIM --> cliCorimCreate - JSONTmplMeta --> cliCorimSign - key --> cliCorimSign - key --> cliCorimVerify - - cliComidCreate --> CBORComid1 - cliCotsCreate --> CBORCots1 - cliCoswidCreate --> CBORSwid1 - - cliCorimCreate --> CBORCorim - cliCorimSign --> CoseSign1 - cliCorimVerify --> signBool - cliCorimSubmit -- to--> provisioningEndpoint - - CBORComid1 --> cliComidDisplay - CBORComid1 --> cliCorimCreate - - CBORCots1 --> cliCorimCreate - CBORCots1 --> cliCotsDisplay - - CBORSwid1 --> cliCoswidDisplay - CBORSwid1 --> cliCorimCreate - - CBORCorim --> cliCorimSubmit - CBORCorim --> cliCorimSign - CoseSign1 --> cliCorimExtract - CoseSign1 --> cliCorimVerify - CoseSign1 --> cliCorimDisplay - - cliCorimExtract --> CBORComid2 - cliCorimExtract --> CBORSwid2 - cliCorimExtract --> CBORCots2 -``` +`cocli` is now available at [https://github.com/veraison/cocli](https://github.com/veraison/cocli) diff --git a/cocli/cmd/comid.go b/cocli/cmd/comid.go deleted file mode 100644 index 0b6c2429..00000000 --- a/cocli/cmd/comid.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "os" - - "github.com/spf13/cobra" -) - -var comidCmd = &cobra.Command{ - Use: "comid", - Short: "CoMID manipulation", - - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - cmd.Help() // nolint: errcheck - os.Exit(0) - } - }, -} - -func init() { - rootCmd.AddCommand(comidCmd) -} diff --git a/cocli/cmd/comidCreate.go b/cocli/cmd/comidCreate.go deleted file mode 100644 index 02b1a4d8..00000000 --- a/cocli/cmd/comidCreate.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/veraison/corim/comid" -) - -var ( - comidCreateFiles []string - comidCreateDirs []string - comidCreateOutputDir string -) - -var comidCreateCmd = NewComidCreateCmd() - -func NewComidCreateCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Short: "create one or more CBOR-encoded CoMID(s) from the supplied JSON template(s)", - Long: `create one or more CBOR-encoded CoMID(s) from the supplied JSON template(s) - - Create CoMIDs from templates t1.json and t2.json, plus any template found in - the templates/ directory. Save them to the current working directory. - - cocli comid create --template=t1.json \ - --template=t2.json \ - --template-dir=templates - - Create one CoMID from template t3.json and save it to the comids/ directory. - Note that the output directory must exist. - - cocli comid create --template=t3.json --output-dir=comids - - Note: since the output file is deterministically generated from the template - file name, all the template file names (when from different directories) - MUST be different. - `, - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkComidCreateArgs(); err != nil { - return err - } - - filesList := filesList(comidCreateFiles, comidCreateDirs, ".json") - if len(filesList) == 0 { - return errors.New("no files found") - } - - errs := 0 - for _, tmplFile := range filesList { - cborFile, err := templateToCBOR(tmplFile, comidCreateOutputDir) - if err != nil { - fmt.Printf(">> creation failed for %q: %v\n", cborFile, err) - errs++ - continue - } - fmt.Printf(">> created %q from %q\n", cborFile, tmplFile) - } - - if errs != 0 { - return fmt.Errorf("%d/%d creations(s) failed", errs, len(filesList)) - } - return nil - }, - } - - cmd.Flags().StringArrayVarP( - &comidCreateFiles, "template", "t", []string{}, "a CoMID template file (in JSON format)", - ) - - cmd.Flags().StringArrayVarP( - &comidCreateDirs, "template-dir", "T", []string{}, "a directory containing CoMID template files", - ) - - cmd.Flags().StringVarP( - &comidCreateOutputDir, "output-dir", "o", ".", "directory where the created files are stored", - ) - - return cmd -} - -func checkComidCreateArgs() error { - if len(comidCreateFiles) == 0 && len(comidCreateDirs) == 0 { - return errors.New("no templates supplied") - } - return nil -} - -func templateToCBOR(tmplFile, outputDir string) (string, error) { - var ( - tmplData, cborData []byte - cborFile string - c comid.Comid - err error - ) - - if tmplData, err = afero.ReadFile(fs, tmplFile); err != nil { - return "", fmt.Errorf("error loading template from %s: %w", tmplFile, err) - } - - if err = c.FromJSON(tmplData); err != nil { - return "", fmt.Errorf("error decoding template from %s: %w", tmplFile, err) - } - - if err = c.Valid(); err != nil { - return "", fmt.Errorf("error validating template %s: %w", tmplFile, err) - } - - cborData, err = c.ToCBOR() - if err != nil { - return "", fmt.Errorf("error encoding template %s to CBOR: %w", tmplFile, err) - } - - cborFile = makeFileName(outputDir, tmplFile, ".cbor") - - err = afero.WriteFile(fs, cborFile, cborData, 0644) - if err != nil { - return "", fmt.Errorf("error saving CBOR file %s: %w", cborFile, err) - } - - return cborFile, nil -} - -func init() { - comidCmd.AddCommand(comidCreateCmd) -} diff --git a/cocli/cmd/comidCreate_test.go b/cocli/cmd/comidCreate_test.go deleted file mode 100644 index 9bac5dca..00000000 --- a/cocli/cmd/comidCreate_test.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/veraison/corim/comid" -) - -func Test_ComidCreateCmd_unknown_argument(t *testing.T) { - cmd := NewComidCreateCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_ComidCreateCmd_no_templates(t *testing.T) { - cmd := NewComidCreateCmd() - - // no args - - err := cmd.Execute() - assert.EqualError(t, err, "no templates supplied") -} - -func Test_ComidCreateCmd_no_files_found(t *testing.T) { - cmd := NewComidCreateCmd() - - args := []string{ - "--template=unknown", - "--template-dir=unsure", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no files found") -} - -func Test_ComidCreateCmd_template_with_invalid_json(t *testing.T) { - var err error - - cmd := NewComidCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "invalid.json", []byte("..."), 0644) - require.NoError(t, err) - - args := []string{ - "--template=invalid.json", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, "1/1 creations(s) failed") -} - -func Test_ComidCreateCmd_template_with_invalid_comid(t *testing.T) { - var err error - - cmd := NewComidCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "bad-comid.json", []byte("{}"), 0644) - require.NoError(t, err) - - args := []string{ - "--template=bad-comid.json", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, "1/1 creations(s) failed") -} - -func Test_ComidCreateCmd_template_from_file_to_default_dir(t *testing.T) { - var err error - - cmd := NewComidCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "ok.json", []byte(comid.PSARefValJSONTemplate), 0644) - require.NoError(t, err) - - args := []string{ - "--template=ok.json", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.NoError(t, err) - - expectedFileName := "ok.cbor" - - _, err = fs.Stat(expectedFileName) - assert.NoError(t, err) -} - -func Test_ComidCreateCmd_template_from_dir_to_custom_dir(t *testing.T) { - var err error - - cmd := NewComidCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "testdir/ok.json", []byte(comid.PSARefValJSONTemplate), 0644) - require.NoError(t, err) - - args := []string{ - "--template-dir=testdir", - "--output-dir=testdir", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.NoError(t, err) - - expectedFileName := "testdir/ok.cbor" - - _, err = fs.Stat(expectedFileName) - assert.NoError(t, err) -} diff --git a/cocli/cmd/comidDisplay.go b/cocli/cmd/comidDisplay.go deleted file mode 100644 index 41683e66..00000000 --- a/cocli/cmd/comidDisplay.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/afero" - "github.com/spf13/cobra" -) - -var ( - comidDisplayFiles []string - comidDisplayDirs []string -) - -var comidDisplayCmd = NewComidDisplayCmd() - -func NewComidDisplayCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "display", - Short: "display one or more CBOR-encoded CoMID(s) in human readable (JSON) format", - Long: `display one or more CBOR-encoded CoMID(s) in human readable (JSON) format. - You can supply individual CoMID files or directories containing CoMID files. - - Display CoMID in file c.cbor. - - cocli comid display --file=c.cbor - - Display CoMIDs in files c1.cbor, c2.cbor and any cbor file in the comids/ - directory. - - cocli comid display --file=c1.cbor --file=c2.cbor --dir=comids - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkComidDisplayArgs(); err != nil { - return err - } - - filesList := filesList(comidDisplayFiles, comidDisplayDirs, ".cbor") - if len(filesList) == 0 { - return errors.New("no files found") - } - - errs := 0 - for _, file := range filesList { - if err := displayComidFile(file); err != nil { - fmt.Printf(">> failed displaying %q: %v\n", file, err) - errs++ - continue - } - } - - if errs != 0 { - return fmt.Errorf("%d/%d display(s) failed", errs, len(filesList)) - } - return nil - }, - } - - cmd.Flags().StringArrayVarP( - &comidDisplayFiles, "file", "f", []string{}, "a CoMID file (in CBOR format)", - ) - - cmd.Flags().StringArrayVarP( - &comidDisplayDirs, "dir", "d", []string{}, "a directory containing CoMID files (in CBOR format)", - ) - - return cmd -} - -func displayComidFile(file string) error { - var ( - data []byte - err error - ) - - if data, err = afero.ReadFile(fs, file); err != nil { - return fmt.Errorf("error loading CoMID from %s: %w", file, err) - } - - // use file name as heading - return printComid(data, ">> ["+file+"]") -} - -func checkComidDisplayArgs() error { - if len(comidDisplayFiles) == 0 && len(comidDisplayDirs) == 0 { - return errors.New("no files supplied") - } - return nil -} - -func init() { - comidCmd.AddCommand(comidDisplayCmd) -} diff --git a/cocli/cmd/comidDisplay_test.go b/cocli/cmd/comidDisplay_test.go deleted file mode 100644 index cc43a92f..00000000 --- a/cocli/cmd/comidDisplay_test.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "fmt" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_ComidDisplayCmd_unknown_argument(t *testing.T) { - cmd := NewComidDisplayCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_ComidDisplayCmd_no_files(t *testing.T) { - cmd := NewComidDisplayCmd() - - // no args - - err := cmd.Execute() - assert.EqualError(t, err, "no files supplied") -} - -func Test_ComidDisplayCmd_no_files_found(t *testing.T) { - cmd := NewComidDisplayCmd() - - args := []string{ - "--file=unknown", - "--dir=unsure", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no files found") -} - -func Test_ComidDisplayCmd_file_with_invalid_cbor(t *testing.T) { - var err error - - cmd := NewComidDisplayCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "invalid.cbor", []byte{0xff, 0xff}, 0400) - require.NoError(t, err) - - args := []string{ - "--file=invalid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, "1/1 display(s) failed") -} - -func Test_ComidDisplayCmd_file_with_valid_comid(t *testing.T) { - var err error - - cmd := NewComidDisplayCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "ok.cbor", PSARefValCBOR, 0400) - require.NoError(t, err) - - args := []string{ - "--file=ok.cbor", - } - cmd.SetArgs(args) - - fmt.Printf("%x\n", PSARefValCBOR) - - err = cmd.Execute() - assert.NoError(t, err) -} - -func Test_ComidDisplayCmd_file_with_valid_comid_from_dir(t *testing.T) { - var err error - - cmd := NewComidDisplayCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "testdir/ok.cbor", PSARefValCBOR, 0400) - require.NoError(t, err) - - args := []string{ - "--dir=testdir", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.NoError(t, err) -} diff --git a/cocli/cmd/comidValidate.go b/cocli/cmd/comidValidate.go deleted file mode 100644 index 0aef8767..00000000 --- a/cocli/cmd/comidValidate.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/veraison/corim/comid" -) - -var ( - comidValidateFiles []string - comidValidateDirs []string -) - -var comidValidateCmd = NewComidValidateCmd() - -func NewComidValidateCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "validate", - Short: "validate one or more CBOR-encoded CoMID(s)", - Long: `validate one or more CBOR-encoded CoMID(s) - - Validate CoMID in file c.cbor. - - cocli comid validate --file=c.cbor - - Validate CoMIDs in files c1.cbor, c2.cbor and any cbor file in the comids/ - directory. - - cocli comid validate --file=c1.cbor --file=c2.cbor --dir=comids - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkComidValidateArgs(); err != nil { - return err - } - - filesList := filesList(comidValidateFiles, comidValidateDirs, ".cbor") - if len(filesList) == 0 { - return errors.New("no files found") - } - - errs := 0 - for _, file := range filesList { - err := validateComid(file) - if err != nil { - fmt.Printf("[invalid] %q: %v\n", file, err) - errs++ - continue - } - fmt.Printf("[valid] %q\n", file) - } - - if errs != 0 { - return fmt.Errorf("%d/%d validation(s) failed", errs, len(filesList)) - } - return nil - }, - } - - cmd.Flags().StringArrayVarP( - &comidValidateFiles, "file", "f", []string{}, "a CoMID file (in CBOR format)", - ) - - cmd.Flags().StringArrayVarP( - &comidValidateDirs, "dir", "d", []string{}, "a directory containing CoMID files (in CBOR format)", - ) - - return cmd -} - -func validateComid(file string) error { - var ( - data []byte - err error - c comid.Comid - ) - - if data, err = afero.ReadFile(fs, file); err != nil { - return fmt.Errorf("error loading CoMID from %s: %w", file, err) - } - - if err = c.FromCBOR(data); err != nil { - return fmt.Errorf("error decoding CoMID from %s: %w", file, err) - } - - if err = c.Valid(); err != nil { - return fmt.Errorf("error validating CoMID %s: %w", file, err) - } - - return nil -} - -func checkComidValidateArgs() error { - if len(comidValidateFiles) == 0 && len(comidValidateDirs) == 0 { - return errors.New("no files supplied") - } - return nil -} - -func init() { - comidCmd.AddCommand(comidValidateCmd) -} diff --git a/cocli/cmd/comidValidate_test.go b/cocli/cmd/comidValidate_test.go deleted file mode 100644 index 6c78c200..00000000 --- a/cocli/cmd/comidValidate_test.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_ComidValidateCmd_unknown_argument(t *testing.T) { - cmd := NewComidValidateCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_ComidValidateCmd_no_files(t *testing.T) { - cmd := NewComidValidateCmd() - - // no args - - err := cmd.Execute() - assert.EqualError(t, err, "no files supplied") -} - -func Test_ComidValidateCmd_no_files_found(t *testing.T) { - cmd := NewComidValidateCmd() - - args := []string{ - "--file=unknown", - "--dir=unsure", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no files found") -} - -func Test_ComidValidateCmd_file_with_invalid_cbor(t *testing.T) { - var err error - - cmd := NewComidValidateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "invalid.cbor", []byte{0xff, 0xff}, 0400) - require.NoError(t, err) - - args := []string{ - "--file=invalid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, "1/1 validation(s) failed") -} - -func Test_ComidValidateCmd_file_with_invalid_comid(t *testing.T) { - var err error - - cmd := NewComidValidateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "bad-comid.cbor", []byte{0xa0}, 0400) - require.NoError(t, err) - - args := []string{ - "--file=bad-comid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, "1/1 validation(s) failed") -} - -func Test_ComidValidateCmd_file_with_valid_comid(t *testing.T) { - var err error - - cmd := NewComidValidateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "ok.cbor", PSARefValCBOR, 0400) - require.NoError(t, err) - - args := []string{ - "--file=ok.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.NoError(t, err) -} - -func Test_ComidValidateCmd_file_with_valid_comid_from_dir(t *testing.T) { - var err error - - cmd := NewComidValidateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "testdir/ok.cbor", PSARefValCBOR, 0400) - require.NoError(t, err) - - args := []string{ - "--dir=testdir", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.NoError(t, err) -} diff --git a/cocli/cmd/common.go b/cocli/cmd/common.go deleted file mode 100644 index adce111e..00000000 --- a/cocli/cmd/common.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "encoding/json" - "fmt" - "path/filepath" - "strings" - - "github.com/spf13/afero" - "github.com/veraison/corim/comid" - "github.com/veraison/corim/cots" - "github.com/veraison/swid" -) - -func filesList(files, dirs []string, ext string) []string { - var l []string - - for _, file := range files { - if _, err := fs.Stat(file); err == nil { - if filepath.Ext(file) == ext { - l = append(l, file) - } - } - } - - for _, dir := range dirs { - filesInfo, err := afero.ReadDir(fs, dir) - if err != nil { - continue - } - - for _, fileInfo := range filesInfo { - if !fileInfo.IsDir() && filepath.Ext(fileInfo.Name()) == ext { - l = append(l, filepath.Join(dir, fileInfo.Name())) - } - } - } - - return l -} - -type FromCBORLoader interface { - FromCBOR([]byte) error -} - -func printJSONFromCBOR(fcl FromCBORLoader, cbor []byte, heading string) error { - var ( - err error - j []byte - ) - - if err = fcl.FromCBOR(cbor); err != nil { - return fmt.Errorf("CBOR decoding failed: %w", err) - } - - indent := " " - if j, err = json.MarshalIndent(fcl, "", indent); err != nil { - return fmt.Errorf("JSON encoding failed: %w", err) - } - - fmt.Println(heading) - fmt.Println(string(j)) - - return nil -} - -func printComid(cbor []byte, heading string) error { - return printJSONFromCBOR(&comid.Comid{}, cbor, heading) -} - -func printCoswid(cbor []byte, heading string) error { - return printJSONFromCBOR(&swid.SoftwareIdentity{}, cbor, heading) -} - -func printCots(cbor []byte, heading string) error { - return printJSONFromCBOR(&cots.ConciseTaStore{}, cbor, heading) -} - -func makeFileName(dirName, baseName, ext string) string { - return filepath.Join( - dirName, - filepath.Base( - strings.TrimSuffix( - baseName, - filepath.Ext(baseName), - ), - )+ext, - ) -} diff --git a/cocli/cmd/corim.go b/cocli/cmd/corim.go deleted file mode 100644 index 2615064f..00000000 --- a/cocli/cmd/corim.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "os" - - "github.com/spf13/cobra" -) - -var corimCmd = &cobra.Command{ - Use: "corim", - Short: "CoRIM manipulation", - - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - cmd.Help() // nolint: errcheck - os.Exit(0) - } - }, -} - -func init() { - rootCmd.AddCommand(corimCmd) -} diff --git a/cocli/cmd/corimCreate.go b/cocli/cmd/corimCreate.go deleted file mode 100644 index c3bfd568..00000000 --- a/cocli/cmd/corimCreate.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/veraison/corim/comid" - "github.com/veraison/corim/corim" - "github.com/veraison/corim/cots" - "github.com/veraison/swid" -) - -var ( - corimCreateCorimFile *string - corimCreateCoswidFiles []string - corimCreateCoswidDirs []string - corimCreateComidFiles []string - corimCreateComidDirs []string - corimCreateCotsFiles []string - corimCreateCotsDirs []string - corimCreateOutputFile *string -) - -var corimCreateCmd = NewCorimCreateCmd() - -func NewCorimCreateCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Short: "create a CBOR-encoded CoRIM from the supplied JSON template, CoMID(s), CoSWID(s) and/or CoTS", - Long: `create a CBOR-encoded CoRIM from the supplied JSON template, CoMID(s), CoSWID(s) and/or CoTS, - - Create a CoRIM from template t1.json, adding CoMIDs found in the comid/ - directory, CoSWIDs found in the coswid/ directory and CoTS found in the cots/ directory. Since no explicit - output file is set, the (unsigned) CoRIM is saved to the current directory - with tag-id as basename and a .cbor extension. - - cocli corim create --template=t1.json --comid-dir=comid --coswid-dir=coswid --cots-dir=cots - - Create a CoRIM from template corim-template.json, adding CoMID stored in - comid1.cbor and the two CoSWIDs stored in coswid1.cbor and dir/coswid2.cbor - and a CoTS stored in cots1.cbor. - The (unsigned) CoRIM is saved to corim.cbor. - - cocli corim create --template=corim-template.json \ - --comid=comid1.cbor \ - --coswid=coswid1.cbor \ - --coswid=dir/coswid2.cbor \ - --cots=cots1.cbor - --output=corim.cbor - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkCorimCreateArgs(); err != nil { - return err - } - - comidFilesList := filesList(corimCreateComidFiles, corimCreateComidDirs, ".cbor") - coswidFilesList := filesList(corimCreateCoswidFiles, corimCreateCoswidDirs, ".cbor") - cotsFilesList := filesList(corimCreateCotsFiles, corimCreateCotsDirs, ".cbor") - - if len(comidFilesList)+len(coswidFilesList)+len(cotsFilesList) == 0 { - return errors.New("no CoMID, CoSWID or CoTS files found") - } - - // checkCorimCreateArgs makes sure corimCreateCorimFile is not nil - cborFile, err := corimTemplateToCBOR(*corimCreateCorimFile, - comidFilesList, coswidFilesList, cotsFilesList, corimCreateOutputFile) - if err != nil { - return err - } - fmt.Printf(">> created %q from %q\n", cborFile, *corimCreateCorimFile) - - return nil - }, - } - - corimCreateCorimFile = cmd.Flags().StringP("template", "t", "", "a CoRIM template file (in JSON format)") - - cmd.Flags().StringArrayVarP( - &corimCreateComidDirs, "comid-dir", "M", []string{}, "a directory containing CBOR-encoded CoMID files", - ) - - cmd.Flags().StringArrayVarP( - &corimCreateComidFiles, "comid", "m", []string{}, "a CBOR-encoded CoMID file", - ) - - cmd.Flags().StringArrayVarP( - &corimCreateCoswidDirs, "coswid-dir", "S", []string{}, "a directory containing CBOR-encoded CoSWID files", - ) - - cmd.Flags().StringArrayVarP( - &corimCreateCoswidFiles, "coswid", "s", []string{}, "a CBOR-encoded CoSWID file", - ) - - cmd.Flags().StringArrayVarP( - &corimCreateCotsDirs, "cots-dir", "C", []string{}, "a directory containing CBOR-encoded CoTS files", - ) - - cmd.Flags().StringArrayVarP( - &corimCreateCotsFiles, "cots", "c", []string{}, "a CBOR-encoded CoTS file", - ) - - corimCreateOutputFile = cmd.Flags().StringP("output", "o", "", "name of the generated (unsigned) CoRIM file") - - return cmd -} - -func checkCorimCreateArgs() error { - if corimCreateCorimFile == nil || *corimCreateCorimFile == "" { - return errors.New("no CoRIM template supplied") - } - - if len(corimCreateComidDirs)+len(corimCreateComidFiles)+ - len(corimCreateCoswidDirs)+len(corimCreateCoswidFiles)+ - len(corimCreateCotsDirs)+len(corimCreateCotsFiles) == 0 { - return errors.New("no CoMID, CoSWID or CoTS files or folders supplied") - } - - return nil -} - -func corimTemplateToCBOR(tmplFile string, comidFiles, coswidFiles, cotsFiles []string, outputFile *string) (string, error) { - var ( - tmplData, corimCBOR []byte - c corim.UnsignedCorim - corimFile string - err error - ) - - if tmplData, err = afero.ReadFile(fs, tmplFile); err != nil { - return "", fmt.Errorf("error loading template from %s: %w", tmplFile, err) - } - - if err = c.FromJSON(tmplData); err != nil { - return "", fmt.Errorf("error decoding template from %s: %w", tmplFile, err) - } - - // append CoMID(s) - for _, comidFile := range comidFiles { - var ( - comidCBOR []byte - m comid.Comid - ) - - comidCBOR, err = afero.ReadFile(fs, comidFile) - if err != nil { - return "", fmt.Errorf("error loading CoMID from %s: %w", comidFile, err) - } - - err = m.FromCBOR(comidCBOR) - if err != nil { - return "", fmt.Errorf("error loading CoMID from %s: %w", comidFile, err) - } - - if c.AddComid(m) == nil { - return "", fmt.Errorf( - "error adding CoMID from %s (check its validity using the %q sub-command)", - comidFile, "comid validate", - ) - } - } - - // append CoSWID(s) - for _, coswidFile := range coswidFiles { - var ( - coswidCBOR []byte - s swid.SoftwareIdentity - ) - - coswidCBOR, err = afero.ReadFile(fs, coswidFile) - if err != nil { - return "", fmt.Errorf("error loading CoSWID from %s: %w", coswidFile, err) - } - - err = s.FromCBOR(coswidCBOR) - if err != nil { - return "", fmt.Errorf("error loading CoSWID from %s: %w", coswidFile, err) - } - - if c.AddCoswid(s) == nil { - return "", fmt.Errorf("error adding CoSWID from %s", coswidFile) - } - } - - // append CoTS(s) - for _, cotsFile := range cotsFiles { - var ( - cotsCBOR []byte - t cots.ConciseTaStore - ) - - cotsCBOR, err = afero.ReadFile(fs, cotsFile) - if err != nil { - return "", fmt.Errorf("error loading CoTS from %s: %w", cotsFile, err) - } - - err = t.FromCBOR(cotsCBOR) - if err != nil { - return "", fmt.Errorf("error loading CoTS from %s: %w", cotsFile, err) - } - - if c.AddCots(t) == nil { - return "", fmt.Errorf("error adding CoTS from %s", cotsFile) - } - } - - // check the result - if err = c.Valid(); err != nil { - return "", fmt.Errorf("error validating CoRIM: %w", err) - } - - corimCBOR, err = c.ToCBOR() - if err != nil { - return "", fmt.Errorf("error encoding CoRIM to CBOR: %w", err) - } - - if outputFile == nil || *outputFile == "" { - corimFile = makeFileName("", tmplFile, ".cbor") - } else { - corimFile = *outputFile - } - - err = afero.WriteFile(fs, corimFile, corimCBOR, 0644) - if err != nil { - return "", fmt.Errorf("error saving CoRIM to file %s: %w", corimFile, err) - } - - return corimFile, nil -} - -func init() { - corimCmd.AddCommand(corimCreateCmd) -} diff --git a/cocli/cmd/corimCreate_test.go b/cocli/cmd/corimCreate_test.go deleted file mode 100644 index 3a68e672..00000000 --- a/cocli/cmd/corimCreate_test.go +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_CorimCreateCmd_unknown_argument(t *testing.T) { - cmd := NewCorimCreateCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_CorimCreateCmd_no_templates(t *testing.T) { - cmd := NewCorimCreateCmd() - - // no args - - err := cmd.Execute() - assert.EqualError(t, err, "no CoRIM template supplied") -} - -func Test_CorimCreateCmd_no_files_found(t *testing.T) { - cmd := NewCorimCreateCmd() - - args := []string{ - "--template=unknown.json", - "--comid=unsure.cbor", - "--comid-dir=somedir", - "--coswid=what.cbor", - "--coswid-dir=someotherdir", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no CoMID, CoSWID or CoTS files found") -} - -func Test_CorimCreateCmd_no_tag_files(t *testing.T) { - cmd := NewCorimCreateCmd() - - args := []string{ - "--template=unknown.json", - // no --co{m,sw}id,{cots}-{dir,} - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no CoMID, CoSWID or CoTS files or folders supplied") -} - -func Test_CorimCreateCmd_template_not_found(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "ignored-comid.cbor", []byte{}, 0644) - require.NoError(t, err) - - args := []string{ - "--template=nonexistent.json", - "--comid=ignored-comid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, "error loading template from nonexistent.json: open nonexistent.json: file does not exist") -} - -func Test_CorimCreateCmd_template_with_invalid_json(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "invalid.json", []byte("..."), 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "ignored-comid.cbor", []byte{}, 0644) - require.NoError(t, err) - - args := []string{ - "--template=invalid.json", - "--comid=ignored-comid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, "error decoding template from invalid.json: invalid character '.' looking for beginning of value") -} - -func Test_CorimCreateCmd_with_a_bad_comid(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "bad-comid.cbor", badCBOR, 0644) - require.NoError(t, err) - - args := []string{ - "--template=min-tmpl.json", - "--comid=bad-comid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, `error loading CoMID from bad-comid.cbor: expected map (CBOR Major Type 5), found Major Type 7`) -} - -func Test_CorimCreateCmd_with_an_invalid_comid(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "invalid-comid.cbor", invalidComid, 0644) - require.NoError(t, err) - - args := []string{ - "--template=min-tmpl.json", - "--comid=invalid-comid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, `error loading CoMID from invalid-comid.cbor: missing mandatory field "Triples" (4)`) -} - -func Test_CorimCreateCmd_with_a_bad_coswid(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "bad-coswid.cbor", badCBOR, 0644) - require.NoError(t, err) - - args := []string{ - "--template=min-tmpl.json", - "--coswid=bad-coswid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, `error loading CoSWID from bad-coswid.cbor: cbor: unexpected "break" code`) -} - -func Test_CorimCreateCmd_with_an_invalid_cots(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "invalid-cots.cbor", invalidCots, 0644) - require.NoError(t, err) - - args := []string{ - "--template=min-tmpl.json", - "--cots=invalid-cots.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, `error adding CoTS from invalid-cots.cbor`) -} - -func Test_CorimCreateCmd_with_a_bad_cots(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "bad-cots.cbor", badCBOR, 0644) - require.NoError(t, err) - - args := []string{ - "--template=min-tmpl.json", - "--cots=bad-cots.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, `error loading CoTS from bad-cots.cbor: cbor: unexpected "break" code`) -} - -func Test_CorimCreateCmd_successful_comid_coswid_and_cots_from_file(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "coswid.cbor", testCoswid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "comid.cbor", testComid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "cots.cbor", testCots, 0644) - require.NoError(t, err) - - args := []string{ - "--template=min-tmpl.json", - "--coswid=coswid.cbor", - "--comid=comid.cbor", - "--cots=cots.cbor", - "--output=corim.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.NoError(t, err) - - _, err = fs.Stat("corim.cbor") - assert.NoError(t, err) -} - -func Test_CorimCreateCmd_successful_comid_coswid_and_cots_from_dir(t *testing.T) { - var err error - - cmd := NewCorimCreateCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "coswid/1.cbor", testCoswid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "comid/1.cbor", testComid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "cots/1.cbor", testCots, 0644) - require.NoError(t, err) - - args := []string{ - "--template=min-tmpl.json", - "--coswid-dir=coswid", - "--comid-dir=comid", - "--cots-dir=cots", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.NoError(t, err) - - _, err = fs.Stat("min-tmpl.cbor") - assert.NoError(t, err) -} diff --git a/cocli/cmd/corimDisplay.go b/cocli/cmd/corimDisplay.go deleted file mode 100644 index 982b6450..00000000 --- a/cocli/cmd/corimDisplay.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/veraison/corim/corim" - "github.com/veraison/corim/cots" -) - -var ( - corimDisplayCorimFile *string - corimDisplayShowTags *bool -) - -var corimDisplayCmd = NewCorimDisplayCmd() - -func NewCorimDisplayCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "display", - Short: "display the content of a CoRIM as JSON", - Long: `display the content of a CoRIM as JSON - - Display the contents of the signed CoRIM signed-corim.cbor - - cocli corim display --file signed-corim.cbor - - Display the contents of the signed CoRIM yet-another-signed-corim.cbor and - also unpack any embedded CoMID, CoSWID and CoTS - - cocli corim display --file yet-another-signed-corim.cbor --show-tags - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkCorimDisplayArgs(); err != nil { - return err - } - - return display(*corimDisplayCorimFile, *corimDisplayShowTags) - }, - } - - corimDisplayCorimFile = cmd.Flags().StringP("file", "f", "", "a signed CoRIM file (in CBOR format)") - corimDisplayShowTags = cmd.Flags().BoolP("show-tags", "v", false, "display embedded tags") - - return cmd -} - -func checkCorimDisplayArgs() error { - if corimDisplayCorimFile == nil || *corimDisplayCorimFile == "" { - return errors.New("no CoRIM supplied") - } - - return nil -} - -func display(signedCorimFile string, showTags bool) error { - var ( - signedCorimCBOR []byte - metaJSON []byte - corimJSON []byte - err error - s corim.SignedCorim - ) - - if signedCorimCBOR, err = afero.ReadFile(fs, signedCorimFile); err != nil { - return fmt.Errorf("error loading signed CoRIM from %s: %w", signedCorimFile, err) - } - - if err = s.FromCOSE(signedCorimCBOR); err != nil { - return fmt.Errorf("error decoding signed CoRIM from %s: %w", signedCorimFile, err) - } - - if metaJSON, err = json.MarshalIndent(&s.Meta, "", " "); err != nil { - return fmt.Errorf("error decoding CoRIM Meta from %s: %w", signedCorimFile, err) - } - - fmt.Println("Meta:") - fmt.Println(string(metaJSON)) - - if corimJSON, err = json.MarshalIndent(&s.UnsignedCorim, "", " "); err != nil { - return fmt.Errorf("error decoding unsigned CoRIM from %s: %w", signedCorimFile, err) - } - - fmt.Println("Corim:") - fmt.Println(string(corimJSON)) - - if showTags { - fmt.Println("Tags:") - for i, e := range s.UnsignedCorim.Tags { - // need at least 3 bytes for the tag and 1 for the smallest bstr - if len(e) < 3+1 { - fmt.Printf(">> skipping malformed tag at index %d\n", i) - continue - } - - // split tag from data - cborTag, cborData := e[:3], e[3:] - - hdr := fmt.Sprintf(">> [ %d ]", i) - - if bytes.Equal(cborTag, corim.ComidTag) { - if err = printComid(cborData, hdr); err != nil { - fmt.Printf(">> skipping malformed CoMID tag at index %d: %v\n", i, err) - } - } else if bytes.Equal(cborTag, corim.CoswidTag) { - if err = printCoswid(cborData, hdr); err != nil { - fmt.Printf(">> skipping malformed CoSWID tag at index %d: %v\n", i, err) - } - } else if bytes.Equal(cborTag, cots.CotsTag) { - if err = printCots(cborData, hdr); err != nil { - fmt.Printf(">> skipping malformed CoTS tag at index %d: %v\n", i, err) - } - } else { - fmt.Printf(">> unmatched CBOR tag: %x\n", cborTag) - } - } - } - - return nil -} - -func init() { - corimCmd.AddCommand(corimDisplayCmd) -} diff --git a/cocli/cmd/corimDisplay_test.go b/cocli/cmd/corimDisplay_test.go deleted file mode 100644 index 419fef6e..00000000 --- a/cocli/cmd/corimDisplay_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_CorimDisplayCmd_unknown_argument(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_CorimDisplayCmd_mandatory_args_missing_corim_file(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{ - "--show-tags", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no CoRIM supplied") -} - -func Test_CorimDisplayCmd_non_existent_corim_file(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{ - "--file=nonexistent.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - - err := cmd.Execute() - assert.EqualError(t, err, "error loading signed CoRIM from nonexistent.cbor: open nonexistent.cbor: file does not exist") -} - -func Test_CorimDisplayCmd_bad_signed_corim(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{ - "--file=bad.txt", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "bad.txt", []byte("hello!"), 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error decoding signed CoRIM from bad.txt: failed CBOR decoding for COSE-Sign1 signed CoRIM: cbor: invalid COSE_Sign1_Tagged object") -} - -func Test_CorimDisplayCmd_invalid_signed_corim(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{ - "--file=invalid.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "invalid.cbor", testSignedCorimInvalid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, `error decoding signed CoRIM from invalid.cbor: failed CBOR decoding of unsigned CoRIM: unexpected EOF`) -} - -func Test_CorimDisplayCmd_ok_top_level_view(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{ - "--file=ok.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) -} - -func Test_CorimDisplayCmd_ok_nested_view(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{ - "--file=ok.cbor", - "--show-tags", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) -} - -func Test_CorimDisplayCmd_ok_top_level_view_with_cots(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{ - "--file=ok.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValidWithCots, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) -} - -func Test_CorimDisplayCmd_ok_nested_view_with_cots(t *testing.T) { - cmd := NewCorimDisplayCmd() - - args := []string{ - "--file=ok.cbor", - "--show-tags", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValidWithCots, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) -} diff --git a/cocli/cmd/corimExtract.go b/cocli/cmd/corimExtract.go deleted file mode 100644 index 3761ee04..00000000 --- a/cocli/cmd/corimExtract.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "bytes" - "errors" - "fmt" - "path/filepath" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/veraison/corim/corim" - "github.com/veraison/corim/cots" -) - -var ( - corimExtractCorimFile *string - corimExtractOutputDir *string -) - -var corimExtractCmd = NewCorimExtractCmd() - -func NewCorimExtractCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "extract", - Short: "extract, as-is, CoSWIDs CoMIDs, CoTS found in a CoRIM and save them to disk", - Long: `extract, as-is, CoSWIDs and CoMIDs, CoTS found in a CoRIM and save them to disk - - Extract the contents of the signed CoRIM signed-corim.cbor to the current - directory - - cocli corim extract --file=signed-corim.cbor - - Extract the contents of the signed CoRIM yet-another-signed-corim.cbor and - store them to directory my-dir. Note that my-dir must exist. - - cocli corim extract --file=yet-another-signed-corim.cbor \ - --output-dir=my-dir - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkCorimExtractArgs(); err != nil { - return err - } - - return extract(*corimExtractCorimFile, corimExtractOutputDir) - }, - } - - corimExtractCorimFile = cmd.Flags().StringP("file", "f", "", "a signed CoRIM file (in CBOR format)") - corimExtractOutputDir = cmd.Flags().StringP("output-dir", "o", ".", "folder to which CoSWIDs, CoMIDs, CoTSs are saved") - - return cmd -} - -func checkCorimExtractArgs() error { - if corimExtractCorimFile == nil || *corimExtractCorimFile == "" { - return errors.New("no CoRIM supplied") - } - - return nil -} - -func extract(signedCorimFile string, outputDir *string) error { - var ( - signedCorimCBOR []byte - err error - s corim.SignedCorim - baseDir string - ) - - if signedCorimCBOR, err = afero.ReadFile(fs, signedCorimFile); err != nil { - return fmt.Errorf("error loading signed CoRIM from %s: %w", signedCorimFile, err) - } - - if err = s.FromCOSE(signedCorimCBOR); err != nil { - return fmt.Errorf("error decoding signed CoRIM from %s: %w", signedCorimFile, err) - } - - baseDir = "." - if outputDir != nil { - baseDir = *outputDir - } - - for i, e := range s.UnsignedCorim.Tags { - var ( - outputFile string - ) - - // need at least 3 bytes for the tag and 1 for the smallest bstr - if len(e) < 3+1 { - fmt.Printf(">> skipping malformed tag at index %d\n", i) - continue - } - - // split tag from data - cborTag, cborData := e[:3], e[3:] - - if bytes.Equal(cborTag, corim.ComidTag) { - outputFile = filepath.Join(baseDir, fmt.Sprintf("%06d-comid.cbor", i)) - - if err = afero.WriteFile(fs, outputFile, cborData, 0644); err != nil { - fmt.Printf(">> error saving CoMID tag at index %d: %v\n", i, err) - } - } else if bytes.Equal(cborTag, corim.CoswidTag) { - outputFile = filepath.Join(baseDir, fmt.Sprintf("%06d-coswid.cbor", i)) - - if err = afero.WriteFile(fs, outputFile, cborData, 0644); err != nil { - fmt.Printf(">> error saving CoSWID tag at index %d: %v\n", i, err) - } - } else if bytes.Equal(cborTag, cots.CotsTag) { - outputFile = filepath.Join(baseDir, fmt.Sprintf("%06d-cots.cbor", i)) - - if err = afero.WriteFile(fs, outputFile, cborData, 0644); err != nil { - fmt.Printf(">> error saving CoTS tag at index %d: %v\n", i, err) - } - } else { - fmt.Printf(">> unmatched CBOR tag: %x\n", cborTag) - } - } - - return nil -} - -func init() { - corimCmd.AddCommand(corimExtractCmd) -} diff --git a/cocli/cmd/corimExtract_test.go b/cocli/cmd/corimExtract_test.go deleted file mode 100644 index 34fabaf7..00000000 --- a/cocli/cmd/corimExtract_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_CorimExtractCmd_unknown_argument(t *testing.T) { - cmd := NewCorimExtractCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_CorimExtractCmd_mandatory_args_missing_corim_file(t *testing.T) { - cmd := NewCorimExtractCmd() - - args := []string{ - "--output-dir=ignore.d/", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no CoRIM supplied") -} - -func Test_CorimExtractCmd_non_existent_corim_file(t *testing.T) { - cmd := NewCorimExtractCmd() - - args := []string{ - "--file=nonexistent.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - - err := cmd.Execute() - assert.EqualError(t, err, "error loading signed CoRIM from nonexistent.cbor: open nonexistent.cbor: file does not exist") -} - -func Test_CorimExtractCmd_bad_signed_corim(t *testing.T) { - cmd := NewCorimExtractCmd() - - args := []string{ - "--file=bad.txt", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "bad.txt", []byte("hello!"), 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error decoding signed CoRIM from bad.txt: failed CBOR decoding for COSE-Sign1 signed CoRIM: cbor: invalid COSE_Sign1_Tagged object") -} - -func Test_CorimExtractCmd_invalid_signed_corim(t *testing.T) { - cmd := NewCorimExtractCmd() - - args := []string{ - "--file=invalid.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "invalid.cbor", testSignedCorimInvalid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, `error decoding signed CoRIM from invalid.cbor: failed CBOR decoding of unsigned CoRIM: unexpected EOF`) -} - -func Test_CorimExtractCmd_ok_save_to_default_dir(t *testing.T) { - cmd := NewCorimExtractCmd() - - args := []string{ - "--file=ok.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) - - _, err = fs.Stat("000000-comid.cbor") - assert.NoError(t, err) - -} - -func Test_CorimExtractCmd_ok_save_to_non_default_dir(t *testing.T) { - cmd := NewCorimExtractCmd() - - args := []string{ - "--file=ok.cbor", - "--output-dir=my-dir/", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) - - _, err = fs.Stat("my-dir/000000-comid.cbor") - assert.NoError(t, err) -} - -func Test_CorimExtractCmd_with_cots_ok_save_to_default_dir(t *testing.T) { - cmd := NewCorimExtractCmd() - - args := []string{ - "--file=ok.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValidWithCots, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) - - _, err = fs.Stat("000000-cots.cbor") - assert.NoError(t, err) - -} diff --git a/cocli/cmd/corimSign.go b/cocli/cmd/corimSign.go deleted file mode 100644 index 74a0c98e..00000000 --- a/cocli/cmd/corimSign.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/veraison/corim/corim" - cose "github.com/veraison/go-cose" -) - -var ( - corimSignCorimFile *string - corimSignKeyFile *string - corimSignOutputFile *string - corimSignMetaFile *string -) - -var corimSignCmd = NewCorimSignCmd() - -func NewCorimSignCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "sign", - Short: "create a signed CoRIM from an unsigned, CBOR-encoded CoRIM using the supplied key", - Long: `create a signed CoRIM from an unsigned, CBOR-encoded CoRIM using the supplied key - - Sign the unsigned CoRIM unsigned-corim.cbor using the key in JWK format from - file key.jwk and save the resulting COSE Sign1 to signed-corim.cbor. Read - the relevant CorimMeta information from file meta.json. - - cocli corim sign --file=unsigned-corim.cbor \ - --key=key.jwk \ - --meta=meta.json \ - --output=signed-corim.cbor - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkCorimSignArgs(); err != nil { - return err - } - - // checkCorimSignArgs makes sure corimSignCorimFile is not nil - coseFile, err := sign(*corimSignCorimFile, *corimSignKeyFile, - *corimSignMetaFile, corimSignOutputFile) - if err != nil { - return err - } - fmt.Printf(">> %q signed and saved to %q\n", *corimSignCorimFile, coseFile) - - return nil - }, - } - - corimSignCorimFile = cmd.Flags().StringP("file", "f", "", "an unsigned CoRIM file (in CBOR format)") - corimSignMetaFile = cmd.Flags().StringP("meta", "m", "", "CoRIM Meta file (in JSON format)") - corimSignKeyFile = cmd.Flags().StringP("key", "k", "", "signing key in JWK format") - corimSignOutputFile = cmd.Flags().StringP("output", "o", "", "name of the generated COSE Sign1 file") - - return cmd -} - -func checkCorimSignArgs() error { - if corimSignCorimFile == nil || *corimSignCorimFile == "" { - return errors.New("no CoRIM supplied") - } - - if corimSignKeyFile == nil || *corimSignKeyFile == "" { - return errors.New("no key supplied") - } - - if corimSignMetaFile == nil || *corimSignMetaFile == "" { - return errors.New("no CoRIM Meta supplied") - } - - return nil -} - -func sign(unsignedCorimFile, keyFile, metaFile string, outputFile *string) (string, error) { - var ( - unsignedCorimCBOR []byte - signedCorimCBOR []byte - metaJSON []byte - keyJWK []byte - err error - signedCorimFile string - c corim.UnsignedCorim - m corim.Meta - signer cose.Signer - ) - - if unsignedCorimCBOR, err = afero.ReadFile(fs, unsignedCorimFile); err != nil { - return "", fmt.Errorf("error loading unsigned CoRIM from %s: %w", unsignedCorimFile, err) - } - - if err = c.FromCBOR(unsignedCorimCBOR); err != nil { - return "", fmt.Errorf("error decoding unsigned CoRIM from %s: %w", unsignedCorimFile, err) - } - - if err = c.Valid(); err != nil { - return "", fmt.Errorf("error validating CoRIM: %w", err) - } - - if metaJSON, err = afero.ReadFile(fs, metaFile); err != nil { - return "", fmt.Errorf("error loading CoRIM Meta from %s: %w", metaFile, err) - } - - if err = m.FromJSON(metaJSON); err != nil { - return "", fmt.Errorf("error decoding CoRIM Meta from %s: %w", metaFile, err) - } - - if err = m.Valid(); err != nil { - return "", fmt.Errorf("error validating CoRIM Meta: %w", err) - } - - if keyJWK, err = afero.ReadFile(fs, keyFile); err != nil { - return "", fmt.Errorf("error loading signing key from %s: %w", keyFile, err) - } - - if signer, err = corim.NewSignerFromJWK(keyJWK); err != nil { - return "", fmt.Errorf("error loading signing key from %s: %w", keyFile, err) - } - - s := corim.SignedCorim{ - UnsignedCorim: c, - Meta: m, - } - - signedCorimCBOR, err = s.Sign(signer) - if err != nil { - return "", fmt.Errorf("error signing CoRIM: %w", err) - } - - if outputFile == nil || *outputFile == "" { - signedCorimFile = "signed-" + unsignedCorimFile - } else { - signedCorimFile = *outputFile - } - - err = afero.WriteFile(fs, signedCorimFile, signedCorimCBOR, 0644) - if err != nil { - return "", fmt.Errorf("error saving signed CoRIM to file %s: %w", signedCorimFile, err) - } - - return signedCorimFile, nil -} - -func init() { - corimCmd.AddCommand(corimSignCmd) -} diff --git a/cocli/cmd/corimSign_test.go b/cocli/cmd/corimSign_test.go deleted file mode 100644 index f8f78ecc..00000000 --- a/cocli/cmd/corimSign_test.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_CorimSignCmd_unknown_argument(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_CorimSignCmd_mandatory_args_missing_corim_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--key=ignored.jwk", - "--meta=ignored.json", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no CoRIM supplied") -} - -func Test_CorimSignCmd_mandatory_args_missing_meta_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ignored.cbor", - "--key=ignored.jwk", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no CoRIM Meta supplied") -} - -func Test_CorimSignCmd_mandatory_args_missing_key_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ignored.cbor", - "--meta=ignored.json", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no key supplied") -} - -func Test_CorimSignCmd_non_existent_unsigned_corim_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=nonexistent.cbor", - "--key=ignored.jwk", - "--meta=ignored.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - - err := cmd.Execute() - assert.EqualError(t, err, "error loading unsigned CoRIM from nonexistent.cbor: open nonexistent.cbor: file does not exist") -} - -func Test_CorimSignCmd_bad_unsigned_corim(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=bad.txt", - "--key=ignored.jwk", - "--meta=ignored.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "bad.txt", []byte("hello!"), 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error decoding unsigned CoRIM from bad.txt: expected map (CBOR Major Type 5), found Major Type 3") -} - -func Test_CorimSignCmd_invalid_unsigned_corim(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=invalid.cbor", - "--key=ignored.jwk", - "--meta=ignored.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "invalid.cbor", testCorimInvalid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, `error decoding unsigned CoRIM from invalid.cbor: missing mandatory field "Tags" (1)`) -} - -func Test_CorimSignCmd_non_existent_meta_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ok.cbor", - "--key=ignored.jwk", - "--meta=nonexistent.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error loading CoRIM Meta from nonexistent.json: open nonexistent.json: file does not exist") -} - -func Test_CorimSignCmd_bad_meta_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ok.cbor", - "--key=ignored.jwk", - "--meta=bad.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "bad.json", []byte("{"), 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error decoding CoRIM Meta from bad.json: unexpected end of JSON input") -} - -func Test_CorimSignCmd_invalid_meta_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ok.cbor", - "--key=ignored.jwk", - "--meta=invalid.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "invalid.json", testMetaInvalid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error validating CoRIM Meta: invalid signer: empty name") -} - -func Test_CorimSignCmd_non_existent_key_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ok.cbor", - "--key=nonexistent.jwk", - "--meta=ok.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "ok.json", testMetaValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error loading signing key from nonexistent.jwk: open nonexistent.jwk: file does not exist") -} - -func Test_CorimSignCmd_invalid_key_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ok.cbor", - "--key=invalid.jwk", - "--meta=ok.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "ok.json", testMetaValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "invalid.jwk", []byte("{}"), 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error loading signing key from invalid.jwk: invalid key type from JSON ()") -} - -func Test_CorimSignCmd_ok_with_default_output_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ok.cbor", - "--key=ok.jwk", - "--meta=ok.json", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "ok.json", testMetaValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "ok.jwk", testECKey, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) - - _, err = fs.Stat("signed-ok.cbor") - assert.NoError(t, err) -} - -func Test_CorimSignCmd_ok_with_custom_output_file(t *testing.T) { - cmd := NewCorimSignCmd() - - args := []string{ - "--file=ok.cbor", - "--key=ok.jwk", - "--meta=ok.json", - "--output=my-signed-corim.cbor", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "ok.json", testMetaValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "ok.jwk", testECKey, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) - - _, err = fs.Stat("my-signed-corim.cbor") - assert.NoError(t, err) -} diff --git a/cocli/cmd/corimSubmit.go b/cocli/cmd/corimSubmit.go deleted file mode 100644 index 80c1bd3a..00000000 --- a/cocli/cmd/corimSubmit.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - "net/url" - "strings" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "github.com/veraison/apiclient/provisioning" -) - -var ( - corimFile *string - mediaType *string - apiServer string - isInsecure bool - certPaths []string -) - -var ( - submitter ISubmitter = &provisioning.SubmitConfig{} - corimSubmitCmd = NewCorimSubmitCmd(submitter) -) - -func NewCorimSubmitCmd(submitter ISubmitter) *cobra.Command { - cmd := &cobra.Command{ - Use: "submit", - Short: "submit a CBOR-encoded CoRIM payload", - Long: `submit a CBOR-encoded CoRIM payload with supplied media type to the given API Server - - To submit the CBOR-encoded CoRIM from file "unsigned-corim.cbor" with media type - "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" to the Veraison - provisioning API endpoint "https://veraison.example/endorsement-provisioning/v1", do: - - - cocli corim submit \ - --corim-file=unsigned-corim.cbor \ - --api-server="https://veraison.example/endorsement-provisioning/v1/submit" \ - --media-type="application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1" - `, - - RunE: func(cmd *cobra.Command, args []string) error { - - if err := checkSubmitArgs(); err != nil { - return err - } - - // Load the data from the CBOR File - data, err := readCorimData(*corimFile) - if err != nil { - return fmt.Errorf("read CoRIM payload failed: %w", err) - } - - if err = provisionData(data, submitter, apiServer, *mediaType); err != nil { - return fmt.Errorf("submit CoRIM payload failed reason: %w", err) - } - return nil - }, - } - - corimFile = cmd.Flags().StringP("corim-file", "f", "", "name of the CoRIM file in CBOR format") - mediaType = cmd.Flags().StringP("media-type", "m", "", "media type of the CoRIM file") - - cmd.Flags().StringP("api-server", "s", "", "API server where to submit the corim file") - cmd.Flags().VarP(&authMethod, "auth", "a", - `authentication method, must be one of "none"/"passthrough", "basic", "oauth2"`) - cmd.Flags().StringP("client-id", "C", "", "OAuth2 client ID") - cmd.Flags().StringP("client-secret", "S", "", "OAuth2 client secret") - cmd.Flags().StringP("token-url", "T", "", "token URL of the OAuth2 service") - cmd.Flags().StringP("username", "U", "", "service username") - cmd.Flags().StringP("password", "P", "", "service password") - cmd.Flags().BoolP( - "insecure", "i", false, "Allow insecure connections (e.g. do not verify TLS certs)", - ) - cmd.Flags().StringArrayP( - "ca-cert", "E", nil, "path to a CA cert that will be used in addition to system certs; may be specified multiple times", - ) - - cmd.Flags().VisitAll(func(flag *pflag.Flag) { - cfgName := strings.ReplaceAll(flag.Name, "-", "_") - err := viper.BindPFlag(cfgName, flag) - cobra.CheckErr(err) - }) - - return cmd -} - -func checkSubmitArgs() error { - if corimFile == nil || *corimFile == "" { - return errors.New("no CoRIM input file supplied") - } - - apiServer = viper.GetString("api_server") - if apiServer == "" { - return errors.New("no API server supplied") - } - u, err := url.Parse(apiServer) - if err != nil || !u.IsAbs() { - return fmt.Errorf("malformed API server URL") - } - - if mediaType == nil || *mediaType == "" { - return errors.New("no media type supplied") - } - - isInsecure = viper.GetBool("insecure") - certPaths = viper.GetStringSlice("ca_cert") - - return nil -} - -func provisionData(data []byte, submitter ISubmitter, uri string, mediaType string) error { - submitter.SetAuth(cliConfig.Auth) - - if err := submitter.SetSubmitURI(uri); err != nil { - return fmt.Errorf("unable to set submit URI: %w", err) - } - - submitter.SetIsInsecure(isInsecure) - submitter.SetCerts(certPaths) - - submitter.SetDeleteSession(true) - if err := submitter.Run(data, mediaType); err != nil { - return fmt.Errorf("run failed: %w", err) - } - - return nil -} - -func readCorimData(file string) ([]byte, error) { - return afero.ReadFile(fs, file) -} - -func init() { - corimCmd.AddCommand(corimSubmitCmd) -} diff --git a/cocli/cmd/corimSubmit_test.go b/cocli/cmd/corimSubmit_test.go deleted file mode 100644 index b5ee04a1..00000000 --- a/cocli/cmd/corimSubmit_test.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "testing" - - "github.com/golang/mock/gomock" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - mock_deps "github.com/veraison/corim/cocli/cmd/mocks" -) - -func Test_CorimSubmitCmd_bad_server_url(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ms := mock_deps.NewMockISubmitter(ctrl) - cmd := NewCorimSubmitCmd(ms) - - args := []string{ - "--corim-file=corim.cbor", - "--api-server=http://www.example.com:80index", - "--media-type=application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "malformed API server URL") -} - -func Test_CorimSubmitCmd_missing_server_url(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ms := mock_deps.NewMockISubmitter(ctrl) - cmd := NewCorimSubmitCmd(ms) - - args := []string{ - "--corim-file=corim.cbor", - "--media-type=application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "no API server supplied") -} - -func Test_CorimSubmitCmd_missing_media_type(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ms := mock_deps.NewMockISubmitter(ctrl) - cmd := NewCorimSubmitCmd(ms) - - args := []string{ - "--corim-file=corim.cbor", - "--api-server=http://www.example.com:8080", - "--media-type=", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "no media type supplied") - -} - -func Test_CorimSubmitCmd_missing_corim_file(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ms := mock_deps.NewMockISubmitter(ctrl) - cmd := NewCorimSubmitCmd(ms) - - args := []string{ - "--corim-file=", - "--api-server=http://www.example.com:8080", - "--media-type=application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no CoRIM input file supplied") - -} - -func Test_CorimSubmitCmd_non_existent_corim_file(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ms := mock_deps.NewMockISubmitter(ctrl) - cmd := NewCorimSubmitCmd(ms) - - args := []string{ - "--corim-file=bad.cbor", - "--api-server=http://www.example.com:8080", - "--media-type=application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "read CoRIM payload failed: open bad.cbor: file does not exist") -} - -func Test_CorimSubmitCmd_submit_ok(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ms := mock_deps.NewMockISubmitter(ctrl) - cmd := NewCorimSubmitCmd(ms) - - args := []string{ - "--corim-file=corim.cbor", - "--api-server=http://veraison.example/endorsement-provisioning/v1/submit", - "--media-type=application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - ms.EXPECT().SetAuth(gomock.Any()) - ms.EXPECT().SetSubmitURI("http://veraison.example/endorsement-provisioning/v1/submit").Return(nil) - ms.EXPECT().SetIsInsecure(false) - ms.EXPECT().SetCerts([]string{}) - ms.EXPECT().SetDeleteSession(true) - ms.EXPECT().Run(testSignedCorimValid, "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1").Return(nil) - err = cmd.Execute() - assert.NoError(t, err) -} - -func Test_CorimSubmitCmd_submit_not_ok(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ms := mock_deps.NewMockISubmitter(ctrl) - cmd := NewCorimSubmitCmd(ms) - - args := []string{ - "--corim-file=corim.cbor", - "--api-server=http://veraison.example/endorsement-provisioning/v1/submit", - "--media-type=application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "corim.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - ms.EXPECT().SetAuth(gomock.Any()) - ms.EXPECT().SetSubmitURI("http://veraison.example/endorsement-provisioning/v1/submit").Return(nil) - ms.EXPECT().SetIsInsecure(false) - ms.EXPECT().SetCerts([]string{}) - ms.EXPECT().SetDeleteSession(true) - err = errors.New(`unexpected HTTP response code 404`) - - ms.EXPECT().Run(testSignedCorimValid, "application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1").Return(err) - err = cmd.Execute() - assert.EqualError(t, err, "submit CoRIM payload failed reason: run failed: unexpected HTTP response code 404") -} diff --git a/cocli/cmd/corimVerify.go b/cocli/cmd/corimVerify.go deleted file mode 100644 index d0df48a7..00000000 --- a/cocli/cmd/corimVerify.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "crypto" - "errors" - "fmt" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/veraison/corim/corim" -) - -var ( - corimVerifyCorimFile *string - corimVerifyKeyFile *string -) - -var corimVerifyCmd = NewCorimVerifyCmd() - -func NewCorimVerifyCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "verify", - Short: "verify a signed CoRIM using the supplied key", - Long: `verify a signed CoRIM using the supplied key - - Verify the signed CoRIM signed-corim.cbor using the key in JWK format from - file key.jwk - - cocli corim verify --file=signed-corim.cbor --key=key.jwk - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkCorimVerifyArgs(); err != nil { - return err - } - - // checkCorimVerifyArgs makes sure corimVerifyCorimFile is not nil - err := verify(*corimVerifyCorimFile, *corimVerifyKeyFile) - if err != nil { - return err - } - fmt.Printf(">> %q verified\n", *corimVerifyCorimFile) - - return nil - }, - } - - corimVerifyCorimFile = cmd.Flags().StringP("file", "f", "", "a signed CoRIM file (in CBOR format)") - corimVerifyKeyFile = cmd.Flags().StringP("key", "k", "", "verification key in JWK format") - - return cmd -} - -func checkCorimVerifyArgs() error { - if corimVerifyCorimFile == nil || *corimVerifyCorimFile == "" { - return errors.New("no CoRIM supplied") - } - - if corimVerifyKeyFile == nil || *corimVerifyKeyFile == "" { - return errors.New("no key supplied") - } - - return nil -} - -func verify(signedCorimFile, keyFile string) error { - var ( - signedCorimCBOR []byte - keyJWK []byte - err error - pkey crypto.PublicKey - s corim.SignedCorim - ) - - if signedCorimCBOR, err = afero.ReadFile(fs, signedCorimFile); err != nil { - return fmt.Errorf("error loading signed CoRIM from %s: %w", signedCorimFile, err) - } - - if err = s.FromCOSE(signedCorimCBOR); err != nil { - return fmt.Errorf("error decoding signed CoRIM from %s: %w", signedCorimFile, err) - } - - if keyJWK, err = afero.ReadFile(fs, keyFile); err != nil { - return fmt.Errorf("error loading verifying key from %s: %w", keyFile, err) - } - - if pkey, err = corim.NewPublicKeyFromJWK(keyJWK); err != nil { - return fmt.Errorf("error loading verifying key from %s: %w", keyFile, err) - } - - if err = s.Verify(pkey); err != nil { - return fmt.Errorf("error verifying %s with key %s: %w", signedCorimFile, keyFile, err) - } - - return nil -} - -func init() { - corimCmd.AddCommand(corimVerifyCmd) -} diff --git a/cocli/cmd/corimVerify_test.go b/cocli/cmd/corimVerify_test.go deleted file mode 100644 index f8e5b26d..00000000 --- a/cocli/cmd/corimVerify_test.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_CorimVerifyCmd_unknown_argument(t *testing.T) { - cmd := NewCorimVerifyCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_CorimVerifyCmd_mandatory_args_missing_corim_file(t *testing.T) { - cmd := NewCorimVerifyCmd() - - args := []string{ - "--key=ignored.jwk", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no CoRIM supplied") -} - -func Test_CorimVerifyCmd_mandatory_args_missing_key_file(t *testing.T) { - cmd := NewCorimVerifyCmd() - - args := []string{ - "--file=ignored.jwk", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no key supplied") -} - -func Test_CorimVerifyCmd_non_existent_signed_corim_file(t *testing.T) { - cmd := NewCorimVerifyCmd() - - args := []string{ - "--file=nonexistent.cbor", - "--key=ignored.jwk", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - - err := cmd.Execute() - assert.EqualError(t, err, "error loading signed CoRIM from nonexistent.cbor: open nonexistent.cbor: file does not exist") -} - -func Test_CorimVerifyCmd_bad_signed_corim(t *testing.T) { - cmd := NewCorimVerifyCmd() - - args := []string{ - "--file=bad.txt", - "--key=ignored.jwk", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "bad.txt", []byte("hello!"), 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error decoding signed CoRIM from bad.txt: failed CBOR decoding for COSE-Sign1 signed CoRIM: cbor: invalid COSE_Sign1_Tagged object") -} - -func Test_CorimVerifyCmd_non_existent_key_file(t *testing.T) { - cmd := NewCorimVerifyCmd() - - args := []string{ - "--file=ok.cbor", - "--key=nonexistent.jwk", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error loading verifying key from nonexistent.jwk: open nonexistent.jwk: file does not exist") -} - -func Test_CorimVerifyCmd_invalid_key_file(t *testing.T) { - cmd := NewCorimVerifyCmd() - - args := []string{ - "--file=ok.cbor", - "--key=invalid.jwk", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "invalid.jwk", []byte("{}"), 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.EqualError(t, err, "error loading verifying key from invalid.jwk: invalid key type from JSON ()") -} - -func Test_CorimVerifyCmd_ok(t *testing.T) { - cmd := NewCorimVerifyCmd() - - args := []string{ - "--file=ok.cbor", - "--key=ok.jwk", - } - cmd.SetArgs(args) - - fs = afero.NewMemMapFs() - err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "ok.jwk", testECKey, 0644) - require.NoError(t, err) - - err = cmd.Execute() - assert.NoError(t, err) -} diff --git a/cocli/cmd/cots.go b/cocli/cmd/cots.go deleted file mode 100644 index 3e9090db..00000000 --- a/cocli/cmd/cots.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "os" - - "github.com/spf13/cobra" -) - -var cotsCmd = &cobra.Command{ - Use: "cots", - Short: "CoTS manipulation", - - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - cmd.Help() // nolint: errcheck - os.Exit(0) - } - }, -} - -func init() { - rootCmd.AddCommand(cotsCmd) -} diff --git a/cocli/cmd/cotsCreate.go b/cocli/cmd/cotsCreate.go deleted file mode 100644 index 9c650a23..00000000 --- a/cocli/cmd/cotsCreate.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - "path/filepath" - - "github.com/google/uuid" - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/veraison/corim/cots" -) - -var ( - cotsCreateLanguage *string - cotsCreateTagID *string - cotsCreateTagUUIDStr *string - cotsCreateTagUUID *bool - cotsCreateTagVersion *uint - cotsCreateCtsEnvFile *string - cotsCreateCtsPermClaimsFile *string - cotsCreateCtsExclClaimsFile *string - cotsCreateCtsPurposes []string - cotsCreateCtsTaDirs []string - cotsCreateCtsTaFiles []string - cotsCreateCtsCaDirs []string - cotsCreateCtsCaFiles []string - cotsCreateCtsOutputFile *string -) - -var cotsCreateCtsCmd = NewCotsCreateCtsCmd() - -func NewCotsCreateCtsCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Short: "create a CBOR-encoded concise-ta-store-map instance from the supplied JSON environment template, JSON constraints templates, purposes, and TAs/CAs", - Long: `create a CBOR-encoded concise-ta-store-map instance from the supplied JSON environment template, JSON constraints templates, purposes, and TAs/CAs, - - Create a concise-ta-store-map from template env-template.json and TAs from tas directory. Since no explicit output file is set, - the result is saved to the current directory with the env-template as basename and a .cbor extension. - - cocli cots create --environment=env-template.json --tas=tas_dir - - Create a concise-ta-store-map from env-template.json, claims-template.json, TAs from tas_dir directory, CAs from cas_dir directory, and - with eat and corim purposes asserted. The result is saved to cots.cbor. - - cocli cots create --environment=env-template.json \ - --purpose=eat \ - --purpose=corim \ - --permclaims=claims-template.json \ - --tas=tas_dir \ - --cas=cas_dir \ - --output=cots.cbor - - Alternatively one can specify individual TA files (in DER Format) or CA files (binary, DER-encoded X.509 Certificate) - - cocli cots create --environment=env-template.json \ - --purpose=eat \ - --purpose=corim \ - --permclaims=claims-template.json \ - --tafile=tas_dir \ - --cafile=cas_dir \ - --output=cots.cbor - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkctsCreateCtsArgs(); err != nil { - return err - } - - certFilesList := filesList(cotsCreateCtsTaFiles, cotsCreateCtsTaDirs, ".der") - taiFilesList := filesList(cotsCreateCtsTaFiles, cotsCreateCtsTaDirs, ".ta") - spkiFilesList := filesList(cotsCreateCtsTaFiles, cotsCreateCtsTaDirs, ".spki") - tasFilesList := append(certFilesList, taiFilesList...) - tasFilesList = append(tasFilesList, spkiFilesList...) - casFilesList := filesList(cotsCreateCtsCaFiles, cotsCreateCtsCaDirs, ".der") - - if len(tasFilesList) == 0 { - return errors.New("no TA files found") - } - - cborFile, err := ctsTemplateToCBOR(*cotsCreateLanguage, *cotsCreateTagID, *cotsCreateTagUUID, *cotsCreateTagUUIDStr, cotsCreateTagVersion, *cotsCreateCtsEnvFile, *cotsCreateCtsPermClaimsFile, *cotsCreateCtsExclClaimsFile, cotsCreateCtsPurposes, - tasFilesList, casFilesList, cotsCreateCtsOutputFile) - if err != nil { - return err - } - fmt.Printf(">> created %q\n", cborFile) - - return nil - }, - } - - cotsCreateLanguage = cmd.Flags().StringP("language", "l", "", "language tag") - cotsCreateTagUUIDStr = cmd.Flags().StringP("uuid-str", "", "", "string representation of a UUID to use as tag ID (mutually exclusive from --uuid and --id)") - cotsCreateTagUUID = cmd.Flags().BoolP("uuid", "", false, "boolean indicating a random UUID value should be used as tag ID (mutually exclusive from --id and --uuid-str)") - cotsCreateTagID = cmd.Flags().StringP("id", "", "", "string value containing a tag ID value (mutually exclusive from --uuid and --uuid-str)") - cotsCreateTagVersion = cmd.Flags().UintP("tag-version", "", 0, "integer value indicating version of tag identity (ignored if neither --uuid nor --id are supplied)") - cotsCreateCtsEnvFile = cmd.Flags().StringP("environment", "e", "", "an environment template file (in JSON format)") - cotsCreateCtsPermClaimsFile = cmd.Flags().StringP("permclaims", "p", "", "a permitted claims template file (in JSON format)") - cotsCreateCtsExclClaimsFile = cmd.Flags().StringP("exclclaims", "x", "", "an excluded claims template file (in JSON format)") - - cmd.Flags().StringArrayVarP( - &cotsCreateCtsPurposes, "purpose", "u", []string{}, "string value indicating purpose: cots,corim,comid,coswid,eat,certificate", - ) - - cmd.Flags().StringArrayVarP( - &cotsCreateCtsTaDirs, "tas", "t", []string{}, "a directory containing binary DER-encoded trust anchor files", - ) - cmd.Flags().StringArrayVarP( - &cotsCreateCtsTaFiles, "tafile", "f", []string{}, "a DER-encoded trust anchor file", - ) - - cmd.Flags().StringArrayVarP( - &cotsCreateCtsCaDirs, "cas", "c", []string{}, "a directory containing binary DER-encoded X.509 CA certificate files", - ) - cmd.Flags().StringArrayVarP( - &cotsCreateCtsCaFiles, "cafile", "", []string{}, "a DER-encoded certificate file", - ) - - cotsCreateCtsOutputFile = cmd.Flags().StringP("output", "o", "", "name of the generated CoTS file") - - return cmd -} - -func IsValidUUID(u string) bool { - _, err := uuid.Parse(u) - return err == nil -} - -func checkctsCreateCtsArgs() error { - if cotsCreateCtsEnvFile == nil || *cotsCreateCtsEnvFile == "" { - return errors.New("no environment template supplied") - } - - if (*cotsCreateTagUUID != false && *cotsCreateTagID != "") || (*cotsCreateTagUUID != false && *cotsCreateTagUUIDStr != "") || (*cotsCreateTagUUIDStr != "" && *cotsCreateTagID != "") { - return errors.New("only one of --uuid, --uuid-str and --id can be used at the same time") - } - - if *cotsCreateTagUUIDStr != "" && !IsValidUUID(*cotsCreateTagUUIDStr) { - return errors.New("--uuid-str does not contain a valid UUID") - } - - if len(cotsCreateCtsTaFiles)+len(cotsCreateCtsTaDirs) == 0 { - return errors.New("no TA files or folders supplied") - } - - return nil -} - -func ctsTemplateToCBOR(language string, tagID string, genUUID bool, uuidStr string, version *uint, envFile string, permClaimsFile string, exclClaimsFile string, purposes, taFiles, caFiles []string, outputFile *string) (string, error) { - var ( - envData []byte - env cots.EnvironmentGroups - permClaimsData []byte - permClaims cots.EatCWTClaim - exclClaimsData []byte - exclClaims cots.EatCWTClaim - err error - ctsCBOR []byte - ctsFile string - ) - - cts := cots.ConciseTaStore{} - - if envData, err = afero.ReadFile(fs, envFile); err != nil { - return "", fmt.Errorf("error loading template from %s: %w", envFile, err) - } - - if err = env.FromJSON(envData); err != nil { - return "", fmt.Errorf("error decoding template from %s: %w", envFile, err) - } - - cts.Environments = env - - if language != "" { - cts.Language = &language - } - - if tagID != "" { - cts.SetTagIdentity(tagID, version) - } else if genUUID != false { - u := uuid.New() - b, _ := u.MarshalBinary() - cts.SetTagIdentity(b, version) - } else if uuidStr != "" { - u, _ := uuid.Parse(uuidStr) - b, _ := u.MarshalBinary() - cts.SetTagIdentity(b, version) - } - - if permClaimsFile != "" { - if permClaimsData, err = afero.ReadFile(fs, permClaimsFile); err != nil { - return "", fmt.Errorf("error loading template from %s: %w", permClaimsFile, err) - } - - if err = permClaims.FromJSON(permClaimsData); err != nil { - return "", fmt.Errorf("error decoding template from %s: %w", permClaimsFile, err) - } - cts.AddPermClaims(permClaims) - } - if exclClaimsFile != "" { - if exclClaimsData, err = afero.ReadFile(fs, exclClaimsFile); err != nil { - return "", fmt.Errorf("error loading template from %s: %w", exclClaimsFile, err) - } - - if err = exclClaims.FromJSON(exclClaimsData); err != nil { - return "", fmt.Errorf("error decoding template from %s: %w", exclClaimsFile, err) - } - cts.AddExclClaims(exclClaims) - } - if 0 != len(purposes) { - cts.Purposes = purposes - } - - k := cots.TasAndCas{} - cts.Keys = &k - for _, taFile := range taFiles { - var ( - tadata []byte - trustAnchor cots.TrustAnchor - ) - - tadata, err = afero.ReadFile(fs, taFile) - if err != nil { - return "", fmt.Errorf("error loading TA from %s: %w", taFile, err) - } - if ".der" == filepath.Ext(taFile) { - trustAnchor.Format = cots.TaFormatCertificate - } - if ".spki" == filepath.Ext(taFile) { - trustAnchor.Format = cots.TaFormatSubjectPublicKeyInfo - } - if ".ta" == filepath.Ext(taFile) { - trustAnchor.Format = cots.TaFormatTrustAnchorInfo - } - trustAnchor.Data = tadata - cts.Keys.Tas = append(cts.Keys.Tas, trustAnchor) - } - - for _, caFile := range caFiles { - var ( - cadata []byte - ) - - cadata, err = afero.ReadFile(fs, caFile) - if err != nil { - return "", fmt.Errorf("error loading CA from %s: %w", caFile, err) - } - cts.Keys.Cas = append(cts.Keys.Cas, cadata) - } - - // check the result - if err = cts.Valid(); err != nil { - return "", fmt.Errorf("error validating CoTS: %w", err) - } - - ctsCBOR, err = cts.ToCBOR() - if err != nil { - return "", fmt.Errorf("error encoding CoTS to CBOR: %w", err) - } - - if outputFile == nil || *outputFile == "" { - ctsFile = makeFileName("", envFile, ".cbor") - } else { - ctsFile = *outputFile - } - - err = afero.WriteFile(fs, ctsFile, ctsCBOR, 0644) - if err != nil { - return "", fmt.Errorf("error saving CoTS to file %s: %w", ctsFile, err) - } - - return ctsFile, nil -} - -func init() { - cotsCmd.AddCommand(cotsCreateCtsCmd) -} diff --git a/cocli/cmd/cotsCreate_test.go b/cocli/cmd/cotsCreate_test.go deleted file mode 100644 index 56a5be8c..00000000 --- a/cocli/cmd/cotsCreate_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" -) - -func Test_CotsCreateCtsCmd_unknown_argument(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_CotsCreateCtsCmd_no_templates(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - // no args - - err := cmd.Execute() - assert.EqualError(t, err, "no environment template supplied") -} - -func Test_CotsCreateCtsCmd_no_files_found(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{ - "--output=output.cbor", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no environment template supplied") -} - -func Test_CotsCreateCtsCmd_env_not_found_no_tas(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{ - "--output=output.cbor", - "--environment=nonexistent.json", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no TA files or folders supplied") -} - -func Test_CotsCreateCtsCmd_too_many_ids(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{ - "--output=output.cbor", - "--uuid", - "--id=some_tag_identity", - "--environment=../data/cots/templates/env/vendor.json", - "--tafile=../data/cots/shared_ta.ta", - } - cmd.SetArgs(args) - fs = afero.NewOsFs() - err := cmd.Execute() - assert.EqualError(t, err, "only one of --uuid, --uuid-str and --id can be used at the same time") -} - -func Test_CotsCreateCtsCmd_invalid_uuid(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{ - "--output=output.cbor", - "--uuid-str=NotAUuid", - "--environment=../data/cots/templates/env/vendor.json", - "--tafile=../data/cots/shared_ta.ta", - } - cmd.SetArgs(args) - fs = afero.NewOsFs() - err := cmd.Execute() - assert.EqualError(t, err, "--uuid-str does not contain a valid UUID") -} - -func Test_CotsCreateCtsCmd_loading_env_template_fail(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{ - "--output=output.cbor", - "--environment=nonexistent.json", - "--tafile=../data/cots/shared_ta.ta", - } - cmd.SetArgs(args) - fs = afero.NewOsFs() - err := cmd.Execute() - assert.EqualError(t, err, "error loading template from nonexistent.json: open nonexistent.json: no such file or directory") -} - -func Test_CotsCreateCtsCmd_loading_permclaims_template_fail(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{ - "--output=output.cbor", - "--environment=../data/cots/templates/env/vendor.json", - "--permclaims=nonexistent.json", - "--tafile=../data/cots/shared_ta.ta", - } - cmd.SetArgs(args) - fs = afero.NewOsFs() - err := cmd.Execute() - assert.EqualError(t, err, "error loading template from nonexistent.json: open nonexistent.json: no such file or directory") -} - -func Test_CotsCreateCtsCmd_loading_exclclaims_template_fail(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{ - "--output=output.cbor", - "--environment=../data/cots/templates/env/vendor.json", - "--exclclaims=nonexistent.json", - "--tafile=../data/cots/shared_ta.ta", - } - cmd.SetArgs(args) - fs = afero.NewOsFs() - err := cmd.Execute() - assert.EqualError(t, err, "error loading template from nonexistent.json: open nonexistent.json: no such file or directory") -} - -func Test_CotsCreateCtsCmd_ok(t *testing.T) { - cmd := NewCotsCreateCtsCmd() - - args := []string{ - "--output=output.cbor", - "--environment=../data/cots/templates/env/vendor.json", - "--exclclaims=../data/cots/templates/claims/exclclaim.json", - "--permclaims=../data/cots/templates/claims/permclaim.json", - "--tafile=../data/cots/shared_ta.ta", - } - cmd.SetArgs(args) - fs = afero.NewOsFs() - err := cmd.Execute() - assert.Nil(t, err) -} diff --git a/cocli/cmd/cotsDisplay.go b/cocli/cmd/cotsDisplay.go deleted file mode 100644 index 54254114..00000000 --- a/cocli/cmd/cotsDisplay.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/afero" - "github.com/spf13/cobra" -) - -var ( - cotsDisplayFiles []string - cotsDisplayDirs []string -) - -var cotsDisplayCmd = NewCotsDisplayCmd() - -func NewCotsDisplayCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "display", - Short: "display one or more CBOR-encoded CoTS(s) in human readable (JSON) format", - Long: `display one or more CBOR-encoded CoTS(s) in human readable (JSON) format. - You can supply individual CoTS files or directories containing CoTS files - - Display CoTS in cots.cbor - - cocli cots display --file=cots.cbor - - `, - - RunE: func(cmd *cobra.Command, args []string) error { - if err := checkCotsDisplayArgs(); err != nil { - return err - } - - filesList := filesList(cotsDisplayFiles, cotsDisplayDirs, ".cbor") - if len(filesList) == 0 { - return errors.New("no files found") - } - - errs := 0 - for _, file := range filesList { - if err := displayCotsFile(file); err != nil { - fmt.Printf(">> failed displaying %q: %v\n", file, err) - errs++ - continue - } - } - - if errs != 0 { - return fmt.Errorf("%d/%d display(s) failed", errs, len(filesList)) - } - return nil - }, - } - - cmd.Flags().StringArrayVarP( - &cotsDisplayFiles, "file", "f", []string{}, "a CoTS file (in CBOR format)", - ) - - cmd.Flags().StringArrayVarP( - &cotsDisplayDirs, "dir", "d", []string{}, "a directory containing CoTS files (in CBOR format)", - ) - - return cmd -} - -func displayCotsFile(file string) error { - var ( - data []byte - err error - ) - - if data, err = afero.ReadFile(fs, file); err != nil { - return fmt.Errorf("error loading CoTS from %s: %w", file, err) - } - - // use file name as heading - return printCots(data, ">> ["+file+"]") - -} - -func checkCotsDisplayArgs() error { - if len(cotsDisplayFiles) == 0 && len(cotsDisplayDirs) == 0 { - return errors.New("no files supplied") - } - - return nil -} - -func init() { - cotsCmd.AddCommand(cotsDisplayCmd) -} diff --git a/cocli/cmd/cotsDisplay_test.go b/cocli/cmd/cotsDisplay_test.go deleted file mode 100644 index 8bcd06c5..00000000 --- a/cocli/cmd/cotsDisplay_test.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "fmt" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_CotsDisplayCmd_unknown_argument(t *testing.T) { - cmd := NewCotsDisplayCmd() - - args := []string{"--unknown-argument=val"} - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "unknown flag: --unknown-argument") -} - -func Test_CotsDisplayCmd_no_files(t *testing.T) { - cmd := NewCotsDisplayCmd() - - err := cmd.Execute() - assert.EqualError(t, err, "no files supplied") -} - -func Test_CotsDisplayCmd_no_files_found(t *testing.T) { - cmd := NewCotsDisplayCmd() - - args := []string{ - "--file=unknown", - "--dir=unsure", - } - cmd.SetArgs(args) - - err := cmd.Execute() - assert.EqualError(t, err, "no files found") -} - -func Test_CotsDisplayCmd_file_with_invalid_cbor(t *testing.T) { - var err error - - cmd := NewCotsDisplayCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "invalid.cbor", []byte{0xff, 0xff}, 0400) - require.NoError(t, err) - - args := []string{ - "--file=invalid.cbor", - } - cmd.SetArgs(args) - - err = cmd.Execute() - assert.EqualError(t, err, "1/1 display(s) failed") -} - -func Test_CotsDisplayCmd_file_with_valid_cots(t *testing.T) { - var err error - - cmd := NewCotsDisplayCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "ok.cbor", testCots, 0400) - require.NoError(t, err) - - args := []string{ - "--file=ok.cbor", - } - cmd.SetArgs(args) - - fmt.Printf("%x\n", testCots) - - err = cmd.Execute() - assert.NoError(t, err) -} - -func Test_CotsDisplayCmd_file_with_valid_cots_from_dir(t *testing.T) { - var err error - - cmd := NewCotsDisplayCmd() - - fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "testdir/ok.cbor", testCots, 0400) - require.NoError(t, err) - - args := []string{ - "--dir=testdir", - } - cmd.SetArgs(args) - - fmt.Printf("%x\n", testCots) - - err = cmd.Execute() - assert.NoError(t, err) -} diff --git a/cocli/cmd/isubmitter.go b/cocli/cmd/isubmitter.go deleted file mode 100644 index cc9734ed..00000000 --- a/cocli/cmd/isubmitter.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "github.com/veraison/apiclient/auth" - "github.com/veraison/apiclient/common" -) - -type ISubmitter interface { - Run([]byte, string) error - SetClient(client *common.Client) error - SetAuth(a auth.IAuthenticator) - SetSubmitURI(uri string) error - SetDeleteSession(session bool) - SetIsInsecure(v bool) - SetCerts(paths []string) -} diff --git a/cocli/cmd/root.go b/cocli/cmd/root.go deleted file mode 100644 index 6297d248..00000000 --- a/cocli/cmd/root.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "errors" - "fmt" - "os" - "path/filepath" - - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/veraison/apiclient/auth" -) - -var ( - cfgFile string - fs = afero.NewOsFs() - - cliConfig = &ClientConfig{} - authMethod = auth.MethodPassthrough -) - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "cocli", - Short: "CoRIM & CoMID swiss-army knife", - Version: "0.0.1", - SilenceUsage: true, - SilenceErrors: true, -} - -type ClientConfig struct { - Auth auth.IAuthenticator -} - -func Execute() { - cobra.CheckErr(rootCmd.Execute()) -} - -func init() { - cobra.OnInitialize(initConfig) - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $XDG_CONFIG_HOME/cocli/config.yaml)") -} - -// initConfig reads in config file and ENV variables if set -func initConfig() { - v, err := readConfig(cfgFile) - cobra.CheckErr(err) - - err = authMethod.Set(v.GetString("auth")) - cobra.CheckErr(err) - - switch authMethod { - case auth.MethodPassthrough: - cliConfig.Auth = &auth.NullAuthenticator{} - case auth.MethodBasic: - cliConfig.Auth = &auth.BasicAuthenticator{} - err = cliConfig.Auth.Configure(map[string]interface{}{ - "username": v.GetString("username"), - "password": v.GetString("password"), - }) - cobra.CheckErr(err) - case auth.MethodOauth2: - cliConfig.Auth = &auth.Oauth2Authenticator{} - err = cliConfig.Auth.Configure(map[string]interface{}{ - "client_id": v.GetString("client_id"), - "client_secret": v.GetString("client_secret"), - "token_url": v.GetString("token_url"), - "username": v.GetString("username"), - "password": v.GetString("password"), - "ca_certs": v.GetStringSlice("ca_cert"), - }) - cobra.CheckErr(err) - default: - // Should never get here as authMethod value is set via - // Method.Set(), which ensures that it's one of the above. - panic(fmt.Sprintf("unknown auth method: %q", authMethod)) - } -} - -func readConfig(path string) (*viper.Viper, error) { - v := viper.GetViper() - if path != "" { - v.SetConfigFile(path) - } else { - wd, err := os.Getwd() - if err != nil { - return nil, err - } - - userConfigDir, err := os.UserConfigDir() - if err == nil { - v.AddConfigPath(filepath.Join(userConfigDir, "cocli")) - } - v.AddConfigPath(wd) - v.SetConfigType("yaml") - v.SetConfigName("config") - } - - v.SetEnvPrefix("cocli") - v.AutomaticEnv() - - err := v.ReadInConfig() - if errors.As(err, &viper.ConfigFileNotFoundError{}) { - err = nil - } - - return v, err -} diff --git a/cocli/cmd/test_vars.go b/cocli/cmd/test_vars.go deleted file mode 100644 index 99b6ee9a..00000000 --- a/cocli/cmd/test_vars.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - _ "embed" - - "github.com/veraison/corim/comid" -) - -var ( - minimalCorimTemplate = []byte(`{ - "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc" - }`) - badCBOR = comid.MustHexDecode(nil, "ffff") - // a "tag-id only" CoMID {1: {0: h'366D0A0A598845ED84882F2A544F6242'}} - invalidComid = comid.MustHexDecode(nil, - "a101a10050366d0a0a598845ed84882f2a544f6242", - ) - // - invalidCots = comid.MustHexDecode(nil, "a2028006a100f6") - // note: embedded CoSWIDs are not validated {0: h'5C57E8F446CD421B91C908CF93E13CFC', 1: [505(h'deadbeef')]} - testCorimValid = comid.MustHexDecode(nil, - "a200505c57e8f446cd421b91c908cf93e13cfc0181d901f944deadbeef", - ) - // {0: h'5C57E8F446CD421B91C908CF93E13CFC'} - testCorimInvalid = comid.MustHexDecode(nil, - "a100505c57e8f446cd421b91c908cf93e13cfc", - ) - testMetaInvalid = []byte("{}") - testMetaValid = []byte(`{ - "signer": { - "name": "ACME Ltd signing key", - "uri": "https://acme.example" - }, - "validity": { - "not-before": "2021-12-31T00:00:00Z", - "not-after": "2025-12-31T00:00:00Z" - } - }`) - - //go:embed testcases/ec-p256.jwk - testECKey []byte - - //go:embed testcases/signed-corim-valid.cbor - testSignedCorimValid []byte - - //go:embed testcases/signed-corim-invalid.cbor - testSignedCorimInvalid []byte - - //go:embed testcases/signed-corim-valid-with-cots.cbor - testSignedCorimValidWithCots []byte - - //go:embed testcases/psa-refval.cbor - PSARefValCBOR []byte - - //go:embed testcases/test-comid.cbor - testComid []byte - - //go:embed testcases/test-coswid.cbor - testCoswid []byte - - //go:embed testcases/test-cots.cbor - testCots []byte -) diff --git a/cocli/cmd/testcases/ec-p256.jwk b/cocli/cmd/testcases/ec-p256.jwk deleted file mode 100644 index e3c07719..00000000 --- a/cocli/cmd/testcases/ec-p256.jwk +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kty": "EC", - "crv": "P-256", - "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", - "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", - "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", - "use": "enc", - "kid": "1" -} diff --git a/cocli/cmd/testcases/psa-refval-orig.cbor b/cocli/cmd/testcases/psa-refval-orig.cbor deleted file mode 100644 index acd824043708af039e2074805c5e0e597566ba72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 416 zcmZ3&keZsO>+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<=axr6)lMhR(k)ENR0c(W9n%#oOpWIR` zVhyr+a!yJm>fs{GbvD_5=T#Jbe|lrri)_Y)OpQ&95en^2{YRA=Yz^7ss?XVcuzym@ znP&L(LG|7${(A{W&Z{1Wnw=65lpn&9YN%(dXG((UOvz0%JGcFv>R+^Lz5X%pLytbl zX6pJr*KB*$(4RN6(H>^HBiM8UpxaGIFn#gT=b|6ZgnJbq{<&{=_91_x|5vwFTNJq* MI>~vfe3nQB0Gw#8o&W#< diff --git a/cocli/cmd/testcases/psa-refval.cbor b/cocli/cmd/testcases/psa-refval.cbor deleted file mode 100644 index e37b711cb6beda921d18a320c0cc29817e076932..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmZ3&keZsO>+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~CL!l(2q@*Hyu%Bt?&9AwzT1LWac*H<=bAO4P$e zmg{V?|IVu@{QmUDt{2&iNlrd2sYZH+dIpROnHrlIBNW=5`j09#*c!6MRiCr@VE?3) zGtKbngX+Ci{Pz-$oL4zTrw#+2MNvvb?uss2T~*6SbhKJ@5= zY^JX7bIrC_4gGmD8|^_(!wd`}LlF{YEU5;-z%U^u=u#YkE`&L4@zUp_AI^k(6(9b& aZ+G?~f203bw^dsdxg0vld8>SuNCg0QKh*F5 diff --git a/cocli/cmd/testcases/regen-from-src.sh b/cocli/cmd/testcases/regen-from-src.sh deleted file mode 100644 index c27f6c69..00000000 --- a/cocli/cmd/testcases/regen-from-src.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/bash -# Copyright 2024 Contributors to the Veraison project. -# SPDX-License-Identifier: Apache-2.0 -set -e - -GEN_TESTCASE=$(go env GOPATH)/bin/gen-testcase - -if [[ ! -f ${GEN_TESTCASE} ]]; then - echo "installing gen-testcase" - - go install github.com/veraison/gen-testcase@v0.0.1 -fi - -testcases=( - psa-refval - test-comid - test-coswid - test-cots -) - -signed_testcases=( - signed-corim-valid - signed-corim-invalid - signed-corim-valid-with-cots -) - -for case in "${testcases[@]}"; do - echo "generating ${case}.cbor" - - ${GEN_TESTCASE} "src/${case}.yaml" -o "${case}.cbor" -done - -for case in "${signed_testcases[@]}"; do - echo "generating ${case}.cbor" - - ${GEN_TESTCASE} -s ec-p256.jwk -m "src/${case}-meta.yaml" "src/${case}.yaml" \ - -o "${case}.cbor" -done - -echo "done." diff --git a/cocli/cmd/testcases/signed-corim-invalid.cbor b/cocli/cmd/testcases/signed-corim-invalid.cbor deleted file mode 100644 index 53fff8fad1da85902d9c27cc916f97ddeca585bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmV;X09pUigjijp0VV@V>Xa%pWVV`6V|2v|X)0HOeNK|@VNAWU>* zAaiMFZfS03AZulL0oWjPXmoUNb2=|CVPkD&E@gOOZE$R50iprH8fjE#fB?Z7Va`YZ zph2KmK+FuATM3=IWAvAgCM%I#mwG1VDfiImdYzd%l)@^fEwK;DibCxVz)bql^|THC UNi{@Ss}~!^X_YsQziU?NU$xam3jhEB diff --git a/cocli/cmd/testcases/signed-corim-valid-with-cots.cbor b/cocli/cmd/testcases/signed-corim-valid-with-cots.cbor deleted file mode 100644 index 69bd669e1b5c24a860b943c51402998b817ee3a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2349 zcmccA5@EWSQH{AIv7jI)GdZy&Ge1wiC^J_(IVr!0BSLZ^!$O7h2qTg zyv)3Gh3wSI1(6)<7BK|GgunRWcGgLH;z^G4lONjrVQh@#n0Ax#_adgog^VE0H}YMy zm|0la7BaLnF-9`oT-3yL)u4&#qCupAFdMs8o5wj@CPp?6FoT(qo!NfOJTp>yM@%pVrct%rHDO7tf1UANuw`A5d-rDA{0e0)A%{D1fQ zfoitvD<_HTi?fJywVeOiFm>@Bvup2`N?e=XJNvqP0?^VJgBU|@15P&PP!={GrqEzR zc>`Gxhl@unI4?gbDYH03!Lgtqu_!f1N5L~MS#G(&7>Y$Gqf> z{GtU-jK2(;80RlwW@2Pw5)qJ|?2>vwz|}=gddJb_2j;EZx3a^43+h!CW+vJ?#z0P- z*T~4g)X>t<)X><#B1)Xs7{oP$a%pTk?P7Pay@4$_+GP1y#BfA4H!#v<`B{L0%+zAQ z58?Yjc+x8Qn~fdfzF$JeW~u8DONsehRvi!;)N(fX=$@@25|=E7s&|380}2ej1IAP6;eOBqN& zQnz4KYH>-Wf=6OrN-;EHs~f0761E(M9D1653QW`8&@|0`$7?}gWq@78r_!rIZpH3< z)^9aN4o_M*0WBq)qo!o@D4N)bJN;4`e`x6!Yh)6iemxirT$vPPmv%q@<)V{w!}Gmq zx{}u<=0mEYooSn-{yKb?)cNc_1DJegsBeC-aP^`A$tRwU*YEJ*n;mCe;*3QWa&mFn7X! z+n|`^#aETymS3Hxw0lW3YWYi{eFW1brRL*KgOo-BS{lR}BgCgccLoC&CIxObbqBWN zFVEzv`s{aUzxIpK?bUq4x=D?adE76T{eJtENg=G!E>Dp6lHCT6Klkj6UJG9MXX9bi zQ&hQjgXvY}SO0^Xm?EO_q`b9VJdC&I-C_#9qkX7I{pN8++aLFZU(DHgcKeDa+jQr= zSUF3#Acnub;dbf)wu_u1N?YoW37)=^ky&gz$HSuK>c0pF;b`gqPhJLaZBBY|?t$a2 z84Kt2F4fuECnj7VvXt5N%g0%9O^?4m+^ID6eScK~^E%$s#^>ZG`lJj%*GxJOI3ySj7GILV(4O!6iur@AcYGh={gqg1gbV3e_)d4Z# zFTS{)b&{TVlH>g3hchdH3Uby+m*0b(e$Be{|{= zV`^NCY9>~1F*P$VGO;XVXl`1_u$VCgT@<9?4Oi!rWrncP`$T`|6an8 z^Qy;z#zih>Nj1_l)H7g>P*}5D@c5HkibbqJHc!q;sYE?oWVy~J`|rGp!tYOS?0S*S znB?RGau;!~z;yj0uzidv0YUj8EUAWi#(Jj2*b4OwQ*zVH&TW6E`WNk5uYb(@(4!Bs znYzBuHQQb_^ykfNw5Qx<#5g9!5$t#aV33)>f_d@M=b|6ZgnJbq{<&{=_91_x|5vwF zTNJq*I>~vfe3nQBQ{y6r8wwQ;uykCISOknWSV{&Z_+tI!`0Gs7ekT@!;e zLtP6Ki&R|$qr?=mREtC-Qo=scQ0lv#qM- zsgiGLghSsev&_|+6M4VXFcfUJ-O{kEajU^eskpmIvo_o+)roxHJbC{@KDE`GIn7V~ a+_m8Mv&ss_Be!>NVo&PY{%OzJlnVerCvFn} diff --git a/cocli/cmd/testcases/src/psa-refval.yaml b/cocli/cmd/testcases/src/psa-refval.yaml deleted file mode 100644 index 9a5d71e7..00000000 --- a/cocli/cmd/testcases/src/psa-refval.yaml +++ /dev/null @@ -1,76 +0,0 @@ ---- -0: en-GB -1: - 0: !!binary |- - Q7vjfy5hSzOu01PP8UKLFg== -2: -- 0: ACME Ltd. - 1: - tag: 32 - value: https://acme.example - 2: - - 0 - - 1 - - 2 -4: - 0: - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK - # acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK - # acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK - # acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/cocli/cmd/testcases/src/signed-corim-invalid-meta.yaml b/cocli/cmd/testcases/src/signed-corim-invalid-meta.yaml deleted file mode 100644 index f3be19ff..00000000 --- a/cocli/cmd/testcases/src/signed-corim-invalid-meta.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -0: - 0: ACME Ltd signing key - 1: - tag: 32 - value: https://acme.example -1: - 0: - tag: 1 - value: 1640908800 - 1: - tag: 1 - value: 1767139200 diff --git a/cocli/cmd/testcases/src/signed-corim-invalid.yaml b/cocli/cmd/testcases/src/signed-corim-invalid.yaml deleted file mode 100644 index 2fbf0ffd..00000000 --- a/cocli/cmd/testcases/src/signed-corim-invalid.yaml +++ /dev/null @@ -1 +0,0 @@ ---- {} diff --git a/cocli/cmd/testcases/src/signed-corim-valid-meta.yaml b/cocli/cmd/testcases/src/signed-corim-valid-meta.yaml deleted file mode 100644 index abffebe3..00000000 --- a/cocli/cmd/testcases/src/signed-corim-valid-meta.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -1: - 0: - tag: 1 - value: 1640908800 - 1: - tag: 1 - value: 1767139200 -0: - 1: - tag: 32 - value: https://acme.example - 0: ACME Ltd signing key diff --git a/cocli/cmd/testcases/src/signed-corim-valid-with-cots-meta.yaml b/cocli/cmd/testcases/src/signed-corim-valid-with-cots-meta.yaml deleted file mode 100644 index e472e598..00000000 --- a/cocli/cmd/testcases/src/signed-corim-valid-with-cots-meta.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -0: - 0: ACME Ltd signing key diff --git a/cocli/cmd/testcases/src/signed-corim-valid-with-cots.yaml b/cocli/cmd/testcases/src/signed-corim-valid-with-cots.yaml deleted file mode 100644 index de094026..00000000 --- a/cocli/cmd/testcases/src/signed-corim-valid-with-cots.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -0: !!binary |- - XFfo9EbNQhuRyQjPk+E8/A== -1: -- !!binary |- - 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 diff --git a/cocli/cmd/testcases/src/signed-corim-valid.yaml b/cocli/cmd/testcases/src/signed-corim-valid.yaml deleted file mode 100644 index 513f2aeb..00000000 --- a/cocli/cmd/testcases/src/signed-corim-valid.yaml +++ /dev/null @@ -1,99 +0,0 @@ -0: !!binary |- - XFfo9EbNQhuRyQjPk+E8/A== -1: -- encodedCBOR: - tag: 506 - value: - 0: en-GB - 1: - 0: !!binary |- - Q7vjfy5hSzOu01PP8UKLFg== - 2: - - 0: ACME Ltd. - 1: - tag: 32 - value: https://acme.example - 2: - - 0 - - 1 - - 2 - 4: - 0: - - - 0: - 0: - tag: 600 - value: acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - - 0: - 0: - tag: 600 - value: acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - - 0: - 0: - tag: 600 - value: acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= -2: -- 0: - tag: 32 - value: https://parent.example/rims/ccb3aa85-61b4-40f1-848e-02ad6e8a254b - 1: - - 1 - - !!binary |- - 5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU= -3: http://arm.com/iot/profile/1 -4: - 0: - tag: 1 - value: 1640908800 - 1: - tag: 1 - value: 1767139200 -5: -- 0: ACME Ltd. - 1: - tag: 32 - value: acme.example - 2: - - 1 - diff --git a/cocli/cmd/testcases/src/test-comid.yaml b/cocli/cmd/testcases/src/test-comid.yaml deleted file mode 100644 index 9a5d71e7..00000000 --- a/cocli/cmd/testcases/src/test-comid.yaml +++ /dev/null @@ -1,76 +0,0 @@ ---- -0: en-GB -1: - 0: !!binary |- - Q7vjfy5hSzOu01PP8UKLFg== -2: -- 0: ACME Ltd. - 1: - tag: 32 - value: https://acme.example - 2: - - 0 - - 1 - - 2 -4: - 0: - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK - # acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK - # acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDEK - # acme-implementation-id-000000001 - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/cocli/cmd/testcases/src/test-coswid.yaml b/cocli/cmd/testcases/src/test-coswid.yaml deleted file mode 100644 index 912c1a67..00000000 --- a/cocli/cmd/testcases/src/test-coswid.yaml +++ /dev/null @@ -1,35 +0,0 @@ ---- -0: com.acme.rrd2013-ce-sp1-v4-1-5-0 -12: 0 -1: ACME Roadrunner Detector 2013 Coyote Edition SP1 -13: 4.1.5 -5: - 43: trial - 45: '2013' - 47: coyote - 52: Roadrunner Detector - 54: sp1 -2: -- 31: The ACME Corporation - 32: acme.com - 33: - - 1 - - 2 -- 31: Coyote Services, Inc. - 32: mycoyote.com - 33: 4 -4: - 38: www.gnu.org/licenses/gpl.txt - 40: license -6: - 16: - 24: rrdetector - 25: "%programdata%" - 26: - 17: - 24: rrdetector.exe - 20: 532712 - 7: - - 1 - - !!binary |- - oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2o= diff --git a/cocli/cmd/testcases/src/test-cots.yaml b/cocli/cmd/testcases/src/test-cots.yaml deleted file mode 100644 index af6af0c2..00000000 --- a/cocli/cmd/testcases/src/test-cots.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -1: - 0: !!binary |- - qw9Esb/cRgSrSjD4BAfrzA== -2: -- 3: Miscellaneous TA Store -6: - 0: - - - 0 # pkix-cert-type - - !!binary |- - MIIBvTCCAWSgAwIBAgIVANCdkL89UlzHc9Ui7XfVniK7pFuIMAoGCCqGSM49BAMCMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMD4xCzAJBgNVBAYMAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR0wGwYDVQQDDBRFeGFtcGxlIFRydXN0IEFuY2hvcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABONRqhA5JAekvQN8oLwRVNDnAfBnTznLLE+SEGks677sHSeXfcVhZXUeDiN7/fsVNumaiEWRQpZh3zXPwL8rUMyjPzA9MB0GA1UdDgQWBBQBXEXJrLBGKnFd1xCgeMAVSfEBPzALBgNVHQ8EBAMCAoQwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiALBidABsfpzG0lTL9Eh9b6AUbqnzF+koEZbgvppvvt9QIgVoE+bhEN0j6wSPzePjLrEdD+PEgyjHJ5rbA11SPq/1M= - - - 1 # pkix-tainfo-type - - !!binary |- - ooICtjCCArIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXz21w12owQAx58euratYWiHEkhxDU9MEgetrvAtGYZxNnkfLCsp9vLcw8ISXC8tL97k9ZCUtnr0MzLw37XKRABBT22tHlEou/DenpU0Ozccb3/+fibjCCAj0wUjELMAkGA1UEBgwCVVMxGjAYBgNVBAoMEVplc3R5IEhhbmRzLCBJbmMuMScwJQYDVQQDDB5aZXN0eSBIYW5kcywgSW5jLiBUcnVzdCBBbmNob3KgggHlMIIBi6ADAgECAhQL3EqgUXlQPljyddVSRnNHvK+1MzAKBggqhkjOPQQDAjBSMQswCQYDVQQGDAJVUzEaMBgGA1UECgwRWmVzdHkgSGFuZHMsIEluYy4xJzAlBgNVBAMMHlplc3R5IEhhbmRzLCBJbmMuIFRydXN0IEFuY2hvcjAeFw0yMjA1MTkxNTEzMDdaFw0zMjA1MTYxNTEzMDdaMFIxCzAJBgNVBAYMAlVTMRowGAYDVQQKDBFaZXN0eSBIYW5kcywgSW5jLjEnMCUGA1UEAwweWmVzdHkgSGFuZHMsIEluYy4gVHJ1c3QgQW5jaG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl89tcNdqMEAMefHrq2rWFohxJIcQ1PTBIHra7wLRmGcTZ5HywrKfby3MPCElwvLS/e5PWQlLZ69DMy8N+1ykQKM/MD0wHQYDVR0OBBYEFPba0eUSi78N6elTQ7Nxxvf/5+JuMAsGA1UdDwQEAwIChDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIB2li+f6RCxs2EnvNWciSpIDwiUViWayGv1A8xks80eYAiEAmCez4KGrolFKOZT6bvqf1sYQuJBfvtk/y1JQdUvoqlg= - - - 1 # pkix-tainfo-type - - !!binary |- - ooIC1TCCAtEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN0f5kzywEzZOYbaV23O3N8cku39JoLNjlHPwECbXDDWp0LpAO1z248/hoy6UW/TZMTPPR/93XwHsG16mSFy8XBBSKhM/5gJWjvDbW7qUY1peNm9cfYDCCAlwwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yoIIB+jCCAZ+gAwIBAgIUEBuTRGXAEEVEHhu4xafAnqm+qYgwCgYIKoZIzj0EAwIwXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMB4XDTIyMDUxOTE1MTMwOFoXDTMyMDUxNjE1MTMwOFowXDELMAkGA1UEBgwCVVMxHzAdBgNVBAoMFlNub2JiaXNoIEFwcGFyZWwsIEluYy4xLDAqBgNVBAMMI1Nub2JiaXNoIEFwcGFyZWwsIEluYy4gVHJ1c3QgQW5jaG9yMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzdH+ZM8sBM2TmG2ldtztzfHJLt/SaCzY5Rz8BAm1ww1qdC6QDtc9uPP4aMulFv02TEzz0f/d18B7BtepkhcvF6M/MD0wHQYDVR0OBBYEFIqEz/mAlaO8NtbupRjWl42b1x9gMAsGA1UdDwQEAwIChDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQC2cf43f3PPlCO6/dxv40ftIgxxToKHF72UzENv7+y4ygIhAIGtC/r6SGaFMaP7zD2EloBuIXTtyWu8Hwl+YGdXRY93 diff --git a/cocli/cmd/testcases/test-comid.cbor b/cocli/cmd/testcases/test-comid.cbor deleted file mode 100644 index 42a79e38bb812fa3ceafc4f2550c66982ded5263..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmZ3&vXG&gvgL@s7ba`ItGHPSQGGhmHSShHL3_>)_TMXW(KPtHlHL_J(&xy~m0@4Sk_?@w>+ zdXWus7^aOxI&~4)LY7oRJ!3snA}nJ}2?)v$f%%3hxoKwSw!c&Ti*~KoKjwYt(FfT~ zUEk-LZLb>o^JX^MQzy(8FMTfh;Y_$!@!_BQc4r^*H~N2dTeU@z%b}B;x5{UUR6xQk z#S!Q*mQ({^sF@Jq5r)*%JY9Du#)S+4&buGi>m_;{ue%(4{-aa37*pe7hD=ZlD)^M7 f=rP_85o%W5B1dW diff --git a/cocli/cmd/testcases/test-coswid.cbor b/cocli/cmd/testcases/test-coswid.cbor deleted file mode 100644 index 775208cc9a761b8aec6eef2c47939616eefe07e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 377 zcmZvYu};E39L9lwI%uK^!(TMv955z$2UT!4$lyRi#KFbkay?6-y=$&lS{!)>$tW6!t4;VcU7 z3jY1a%a{GuonOCqc3$Vz0l?s|n~4yUE}_ijBuS6z#xZ6b`<{VE4Q${huCHgZQT?>l zX`>5CO%({i6*^nG(V~G`ax67RZPV7a6sXrVHh6;=q*-``6PCcB%C)ko12|Pc?S`cl zX!!p?bCVd#`m)bas%=T-NktcEK>c)TvKjU!*<1|BamT+bl7@LG2g^A<+XlrPFTSeuw*2ZmrQJ)SI}EtkIJDY4 z&e^gsGa1+!avN~6F^96S@i2u38wwckfjC?|?5-7wxdl0?hO!3IAQ5IB5vYhlNKt8V ziGpKZaz=iUft)z6k&%I^p{1dzp|OE^lsK<3h-(JrQfz*tfiN38*tJZIP-inUvNJm| zusjZ2C19z-zGN?R&4N9GAs3!Aen|JXJgwtDNgz|_^}aW<>eFkFCZ?9k@hMmT{Vi(t za#n}yM5k$q_f5|q*smRMX0g42t${4iZ?b$WVk{zzF|H@qY;e;mjJ+;Hv)`rt+Al`8 zSMv?)CN)atalc&l`|VdIg|J4uJVD+|b{jnY+_N)!EqLLdjfYWBQRUhVrdO3;{SR(p zjAYuisEKKtK@-y^1Khr!em=M0dX|9$PvytgtFx|&brh-Wf=6OrO0kZDXI`?Np}K)8NRF9D4nqzlnJj2x zdD{!hOeUL11NoUBsu-t3htX?t9j6HAW6iS~!7VN~M9fX&8TKsTXTx5}$fK z7z|vQ6l9lnKmX;TlXJuKy=l6V*Cggcs-m4~o233ae3sPt>^_4@kzt1V<_8N`FADUs zoboI0*Zgb81a?e_-*?mgbWlL4_ls2#pyYcKn0&7SQ_@8|-amWsU&?tMma~&*r4rYs_s+g9p8&Kp#vleY`O1UyI57E&1?S}_C1n<8C^!}r zBo?LSK+~{}ffghUD`QuHo{)b56Y_j$LKcvo?2>vwz|}=gddJb_2j;EZw-U8@rKw}k zlC%YC;cF2^+n}If?4l)ZtWiyT;`U@Pa04dYZH52L>x<7%QQr0UPX1%}w@N&PeogJ- xd#9Xn&VT=A$0?wa#jS(!*W*%K;TGgu4I$ diff --git a/cocli/data/comid/1.cbor b/cocli/data/comid/1.cbor deleted file mode 100644 index 35e0cb60c4de7c7d1b865cab7a995b62b114ac9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 531 zcmZ3&keZsO>+Zz3kRiYJ;oafB^f0p z1;tkS`iaT8sd}juiMa(isZ7lbj7%&GnVOmwF)U`d$)p~k0Fu_t1WD$m=9MItWaj7T zW~S&GK!G7+3dmHZtf2hFl%UeQywoDbn@nmEij3!8udD7=+p731z!D1}3`%raQX2K?n}rpGcw|TAm>ZX8n>x8v zll{|hl4 zW#xhU)m%?avJYR`{pQj%qWx$VR$iEDo>6J+ZxLCLsO=Q$8sZb0<>VJ?T;v)N7Ft+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<{EU6hP9tnIOsB)Vz|!lFa-( z-OLnS11KVliWqlMhR(k)ENR0c(W9n%#oOpWIR` zVhyr+a!yJm>fs{GbvD_5=T#Jbe|lrri)_Y)OpQ&95en^2{YRA=Yz^7ss?XVcuzym@ znP&L(LG|7${(A{W&Z{1Wnw=65lpn&9YN%(dXG((UOvz0%JGcFv>R+^Lz5X%pLytbl zX6pJr*KB*$(4RN6(H>^HBiM8UpxaGIFn#gT=b|6ZgnJbq{<&{=_91_x|5vwFTNJq* MI>~vfe3nQB09sV7Y5)KL diff --git a/cocli/data/comid/comid-cca-mult-refval.cbor b/cocli/data/comid/comid-cca-mult-refval.cbor deleted file mode 100644 index 12761868beeed4160df8c0ff3fb7b1b0adf5fd0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 457 zcmZ3&keZsO>+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<=fs{GbvD_5=T#Jbe|lrri)_Y)OpQ&95en^2{YRA=Yz^7ss?XVcuzym@ znP&L(LG|7${(A{W&Z{1Wnw=65lpn&9YN%(dXG((UOvz0%JGcFv>R+^Lz5X%pLytbl zX6pJr*KB*$(4RN6(H>^HBiM8UpxaGIFn#gT=b|6ZgnJbq{<&{=_91_x|5vwFTNJq* sI>~vfe3nQB$n>bJ+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<=Zx5ymkwHGzz4cj`Z?)L?7K7FT`F z=7ar{QqDBPrw^+4R`K6UIC5U~I7m$x>$$8_>#L_#+;=2S@2xrQw*0d-^ZUIUbfziX J>YH`I7695=SZ)9S diff --git a/cocli/data/comid/comid-psa-iakpub.cbor b/cocli/data/comid/comid-psa-iakpub.cbor deleted file mode 100644 index 4975373f7cfcaf72e8c4b4eb0f349ac6f7d67862..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 637 zcmZ3&keZsO>+Zz3kRiYJ;oafB^f0p z1;tkS`iaT8sd}juiMa(isZ7lbj7%&GnVOmwF)U`d$rKTx0Fu_t1WD$m=9MItWaj7T zW~S&GK!G7+3dmHZtf2hFl%UeQywoDbn@nmEij3!8udD7K{S*R1oqRl<6}(*|!Gc`AZrSCo<&hrV`B9!# zSq9FIfsvk2wu@t6vP)pPqpMqlS@T@n0cPIe`;!~L8fP6cDS*JZ&Zk}Ye;5ZrdLY3 ztt}VW39f!F=&qtd!1yeF@|F9O$KmPiOuQNYFT`||l?U!ub3HZ5K73{On@iKE5-?_A z<%Ox{8I{KV7Lf&s+D@UaAwHp5PJW@rMXnKHp_L_;5gtM29$w)-?&c98-hnBWnTFY6 nK@q7=0WLmnd1;YFrH1;cr4>PWUcTlYmZAA>E+Zz3kRiYJ;oafB^f0p z1;tkS`iaT8sd}juiMa(isZ7lbj7%&GnVOmwF)U`d$rKTx0Fu_t1WD$m=9MItWaj7T zW~S&GK!G7+3dmHZtf2hFl%UeQywoDbn@nmEij3!8udD7K{S*R1oqRl<6}(*|!Gc`AZrSCo<&hrV`B9!# zSq9FIfsvk2wu@t6vP)pPqpPpClXHllXR>!_uuEp9pX)HTE>G|R~^)VRnsA}qAB#4^Go$lSv#+{fKKBE&l| t#WK?{J1i(7)hWQm$1N`{vZ&NhKee2&IJGf diff --git a/cocli/data/comid/comid-psa-refval.cbor b/cocli/data/comid/comid-psa-refval.cbor deleted file mode 100644 index acd824043708af039e2074805c5e0e597566ba72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 416 zcmZ3&keZsO>+Zz3kRiZ%_v3oKL~rADmxIrLbm|skYFx~a>FDh1s^C+SqQ`hcp(LZE zq@dVJUq3N9H&rjSA~ClhCzYw0fsu)2Awy%+LWac*H<=axr6)lMhR(k)ENR0c(W9n%#oOpWIR` zVhyr+a!yJm>fs{GbvD_5=T#Jbe|lrri)_Y)OpQ&95en^2{YRA=Yz^7ss?XVcuzym@ znP&L(LG|7${(A{W&Z{1Wnw=65lpn&9YN%(dXG((UOvz0%JGcFv>R+^Lz5X%pLytbl zX6pJr*KB*$(4RN6(H>^HBiM8UpxaGIFn#gT=b|6ZgnJbq{<&{=_91_x|5vwFTNJq* MI>~vfe3nQB0Gw#8o&W#< diff --git a/cocli/data/comid/templates/comid-cca-mult-refval.json b/cocli/data/comid/templates/comid-cca-mult-refval.json deleted file mode 100644 index 229c3b57..00000000 --- a/cocli/data/comid/templates/comid-cca-mult-refval.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "lang": "en-GB", - "tag-identity": { - "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", - "version": 0 - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "https://acme.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" - }, - "vendor": "ACME", - "model": "RoadRunner" - } - }, - "measurements": [ - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "BL", - "version": "2.1.0", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, - "value": { - "digests": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - ] - } - }, - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "PRoT", - "version": "1.3.5", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, - "value": { - "digests": [ - "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" - ] - } - }, - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "ARoT", - "version": "0.1.4", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, - "value": { - "digests": [ - "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" - ] - } - }, - { - "key": { - "type": "cca.platform-config-id", - "value": "cfg v1.0.0" - }, - "value": { - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" - } - } - } - ] - } - ] - } -} - diff --git a/cocli/data/comid/templates/comid-cca-realm-refval.json b/cocli/data/comid/templates/comid-cca-realm-refval.json deleted file mode 100644 index 965d9fd2..00000000 --- a/cocli/data/comid/templates/comid-cca-realm-refval.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "lang": "en-GB", - "tag-identity": { - "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", - "version": 0 - }, - "entities": [ - { - "name": "Workload Client Ltd.", - "regid": "https://workloadclient.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "uuid", - "value": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C" - }, - "vendor": "Workload Client Ltd" - }, - "instance": { - "type": "bytes", - "value": "QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" - } - }, - "measurements": [ - { - "value": { - "integrity-registers": { - "rim": { - "key-type": "text", - "value": [ - "sha-384;QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" - ] - }, - "rem0": { - "key-type": "text", - "value": [ - "sha-384;IQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" - ] - }, - "rem1": { - "key-type": "text", - "value": [ - "sha-384;JQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" - ] - }, - "rem2": { - "key-type": "text", - "value": [ - "sha-384;MQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" - ] - }, - "rem3": { - "key-type": "text", - "value": [ - "sha-384;NQe752H8pS2VE2oTVNt6TdV7Gya+DT2nHZ6yOYazS6YVq/ZRTPNeWp6lWgMtBop4" - ] - } - } - } - } - ] - } - ] - } -} \ No newline at end of file diff --git a/cocli/data/comid/templates/comid-cca-refval.json b/cocli/data/comid/templates/comid-cca-refval.json deleted file mode 100644 index 974a473b..00000000 --- a/cocli/data/comid/templates/comid-cca-refval.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "lang": "en-GB", - "tag-identity": { - "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", - "version": 0 - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "https://acme.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" - }, - "vendor": "ACME", - "model": "RoadRunner" - } - }, - "measurements": [ - { - "key": { - "type": "cca.platform-config-id", - "value": "cfg v1.0.0" - }, - "value": { - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" - } - } - } - ] - } - ] - } -} - diff --git a/cocli/data/comid/templates/comid-dice-refval.json b/cocli/data/comid/templates/comid-dice-refval.json deleted file mode 100644 index d598c2d1..00000000 --- a/cocli/data/comid/templates/comid-dice-refval.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "tag-identity": { - "id": "1D5A8C7C-1C70-4C56-937E-3C5713AE5A83" - }, - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "uuid", - "value": "DD6661F0-0928-4401-966B-589EA74E3272" - }, - "model": "FMC", - "layer": 0, - "index": 0 - } - }, - "measurements": [ - { - "value": { - "op-flags": [ - "notSecure", - "debug" - ], - "digests": [ - "sha-256:RKozavTLFKh5Qy5T3WVxx/qbzK+3X0iCWSYtbqOk2Rs=" - ], - "svn": { - "type": "exact-value", - "value": 10 - } - } - } - ] - }, - { - "environment": { - "class": { - "id": { - "type": "uuid", - "value": "FFDA7CF3-2333-4A91-99A8-068626203ACA" - }, - "model": "L1", - "layer": 1, - "index": 0 - } - }, - "measurements": [ - { - "value": { - "op-flags": [ - "notSecure", - "debug" - ], - "digests": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", - "sha-256:VgXOanU71cskR7hhl418y0an8zsD772wLJYg2o6awD0=" - ], - "svn": { - "type": "exact-value", - "value": 2 - } - } - } - ] - } - ] - } -} diff --git a/cocli/data/comid/templates/comid-psa-iakpub.json b/cocli/data/comid/templates/comid-psa-iakpub.json deleted file mode 100644 index 47ef11d3..00000000 --- a/cocli/data/comid/templates/comid-psa-iakpub.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "lang": "en-GB", - "tag-identity": { - "id": "366D0A0A-5988-45ED-8488-2F2A544F6242", - "version": 0 - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "https://acme.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "attester-verification-keys": [ - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" - }, - "vendor": "ACME", - "model": "RoadRunner" - }, - "instance": { - "type": "ueid", - "value": "Ac7rrnuJJ6MiflMDz14PH3s0u1Qq1yUKwD+83jbsLxUI" - } - }, - "verification-keys": [ - { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" - } - ] - }, - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" - }, - "vendor": "ACME", - "model": "RoadRunner" - }, - "instance": { - "type": "ueid", - "value": "AUyj5PUL8kjDl4cCDWj/0FyIdndRvyZFypI/V6mL7NKW" - } - }, - "verification-keys": [ - { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Vwqe7hy3O8Ypa+BUETLUjBNU3rEXVUyt9XHR7HJWLG7XTKQd9i1kVRXeBPDLFnfYru1/euxRnJM7H9UoFDLdA==\n-----END PUBLIC KEY-----" - } - ] - } - ] - } -} diff --git a/cocli/data/comid/templates/comid-psa-integ-iakpub.json b/cocli/data/comid/templates/comid-psa-integ-iakpub.json deleted file mode 100644 index 2f421794..00000000 --- a/cocli/data/comid/templates/comid-psa-integ-iakpub.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "lang": "en-GB", - "tag-identity": { - "id": "366D0A0A-5988-45ED-8488-2F2A544F6242", - "version": 0 - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "https://acme.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "attester-verification-keys": [ - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" - }, - "vendor": "ACME", - "model": "RoadRunner" - }, - "instance": { - "type": "ueid", - "value": "Ac7rrnuJJ6MiflMDz14PH3s0u1Qq1yUKwD+83jbsLxUI" - } - }, - "verification-keys": [ - { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMKBCTNIcKUSDii11ySs3526iDZ8A\niTo7Tu6KPAqv7D7gS2XpJFbZiItSs3m9+9Ue6GnvHw/GW2ZZaVtszggXIw==\n-----END PUBLIC KEY-----" - } - ] - }, - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" - }, - "vendor": "ACME", - "model": "RoadRunner" - }, - "instance": { - "type": "ueid", - "value": "AUyj5PUL8kjDl4cCDWj/0FyIdndRvyZFypI/V6mL7NKW" - } - }, - "verification-keys": [ - { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Vwqe7hy3O8Ypa+BUETLUjBNU3rEXVUyt9XHR7HJWLG7XTKQd9i1kVRXeBPDLFnfYru1/euxRnJM7H9UoFDLdA==\n-----END PUBLIC KEY-----" - } - ] - } - ] - } -} diff --git a/cocli/data/comid/templates/comid-psa-refval.json b/cocli/data/comid/templates/comid-psa-refval.json deleted file mode 100644 index 8fd66fbc..00000000 --- a/cocli/data/comid/templates/comid-psa-refval.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "lang": "en-GB", - "tag-identity": { - "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", - "version": 0 - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "https://acme.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" - }, - "vendor": "ACME", - "model": "RoadRunner" - } - }, - "measurements": [ - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "BL", - "version": "2.1.0", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, - "value": { - "digests": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - ] - } - }, - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "PRoT", - "version": "1.3.5", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, - "value": { - "digests": [ - "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" - ] - } - }, - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "ARoT", - "version": "0.1.4", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, - "value": { - "digests": [ - "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" - ] - } - } - ] - } - ] - } -} diff --git a/cocli/data/config/example-config.yaml b/cocli/data/config/example-config.yaml deleted file mode 100644 index d067efbc..00000000 --- a/cocli/data/config/example-config.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# This file contains example configuration for connecting to Veraison services. -# This configuration is only necessary for the `cocli corim submit` sub-command, -# as that is the only instance where remote service configuration is used. You -# do not need this configuration for creating or manipulating corims/corim and -# related objects locally. - -# API Server submit endpoint URL. -api_server: https://veraison.example/endorsement-provisioning/v1/submit - -# Authentication method used by the remote service. -auth: none # may also be "basic" or "oauth2" - -# Credentials for the remote service. -username: example_user # used only if auth is "basic" or "oauth2" -password: Passw0rd! # used only if auth is "basic" or "oauth2"; this can also - # be specfied on the command line using --password, or by - # setting COCLI_PASSWORD environment variable - -# OAuth2 cofiguration for the authorisation server associated with the remote -# service. -client_id: veraison-client # used only if auth is "oauth2" -client_secret: YifmabB4cVSPPtFLAmHfq7wKaEHQn10Z # used only if auth is "oauth2" -token_url: http://localhost:11111/realms/veraison/protocol/openid-connect/token # used only if auth is "oauth2" - diff --git a/cocli/data/corim/corim-full.cbor b/cocli/data/corim/corim-full.cbor deleted file mode 100644 index 1f895aeb7ff6cd2a1b3011c16ecde85c1621cbc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1495 zcmcIkYfKbp6rFEoS>CSzbp){^2ohm-XZAq>c_=T}hSjYg0%F@?c32j6XP4PoSRSSB zYSbTEi#C|r)Uvy%#I%;AX>Hn4FsTv?0%<}E8nsQe6(1>$lBPULXSUneAMG#GUo-d4 znRD(v_kQ15NLRc2-jmX6g$dnPgCyr>W}nx$O!Lrc1R&2qb}=Zr}kka9RlLbb%_Y_`I_5 z$!KhUTam2%Ceu8!@XpwW7hlo1s#9^++c%T&H{Z(j&d15h7rWo?mmW;Ta=wP^%18t} zrGsIYNO!34*yW^sd8G8^j`7^Nf;;x$dh+h~9fR$Ww_g2fB4tFxR0fPU+BZ#&uHX78 zb@FKEXz97fn~+}y2QqrF>u;PI&Z`EUGvN1r$br!ovy7t6jEv*-3W8MPG=tl$B;KOJ zNnDK+VGwW-TZ*(L*iH+j=j<%Ya9AVH7WW0k%(+vKU)E(tT(MG`#f1BuPbBdL-7&ywTr+6v_jdw>22=*yL#yFVRcqjf{rSiQdisT&ejryB0^xZrle1-B5@M2s|W<3Y`M9t@;Gz83@pB88CT$Cu7WWv>tH z8$>#Lzlg4w{29y{c4Uuc>^L4}%y{tS!^90IU%N~(_O%ho9m|VxnX{)3mvk5QP`A|+ z!$ax1F-a__ExG#sKx6{R#>MQ9$IAp+vA0*(H&N!Mn=IC?Y}(Lc#(| z9F*B=V#q`yLChdwvHlEJ!P#v*wuPk|EL^V(3=%G&??qq$!*S5$yKfHV?W`TKO(i{Q zojRTLdEcJHM2No|2?9T>9+xI#gw$!tH1RB2o-u09cU(5Qy;jr;o^1h$x1!Q0a!v(vjIQb4uAz@u6=Q zKwS6$4o=U#IQVu&k z&@|@Q+`#izTaH|Ar8tJ=g=P0mU(OlLHaSh#DJd#Tjc1Z|DqKa>lX#XYi@^y6rO#xt zD1};82gE~hXAd_&Jp3_tLwhvm;_U00$9~&hr)qa;AAZt);BJ!EjyN5tC?x1moLM}# wa;uG!8!fz?lz5;^@zl5m0+-^A6^4BQPA_EiT@PivUIwNylkZ~i7X~}QpIur4LjV8( diff --git a/cocli/data/corim/signed-corim-bad-signature.cbor b/cocli/data/corim/signed-corim-bad-signature.cbor deleted file mode 100644 index 8116a8c37173a82225f4d23b70116effd29bace1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 922 zcmZWnO-~b16rFZ}6c)OWh2bN4LEDh%beQSTGN_prAz++f5CHkGpHfC=bU@*`?~MG_w$YU?Ci^FA|r>~_gk*b zL$}jb1J}D{Tb&?fmM|(2!cw_@7CsL{zq_ES-1eNT)90Sw za%hCsO>oewT5hAn`TRz;H&m&-~1 zEeEq!#L5}SvNp1N0;P7{0eYi#4FaR=@m0{ih|}L*<1(AWhz@&;B6ss1lsW^WqUaf5 zi}Km&r`vGYuXiG|EXNI@yRd#SesiA(PCJZWojy)iwQVcMd0}43YnGuHb9GHA7zIa} z)A>Q(DR6z>u+BSeGHkXJh$943Pz9(NuvdAZ80hTx=<@|$*6w(! z-{q>?2~{or43~c(wE!l$@t4k0HLUgT|D}E>%p&+cAdIC?cHm`1roEqUAenTLFQ~F z>Y~o63vMoRw8bAWnSf0}gtUZ;k;e{~sbT8zEq{aEAg;Urm9u99~7O z9}L7EY{}P@U1gg3AAWG=)1$8ydn_q9=kFR$c*^(Wa@+mc=`Zhi|Dbwf3bybxTvI|K z;29kZdlb4&dE5Jv)@PCGSJw_^j^*5NhS!oee^}eoI_28y-=0hES1=VJgZ0iuLj!Y` zd`a!z);3Ud;Qk`ym!9sl4(#&VdwR1hplulZaS(E0G;h~Xw4Koif>lS5={U{cqJzZO z=y4J^;6x+@TtvXU6+C4XoE*mpSOFt3w8RUT@&}gBx9}2!6tm_2R$Kp+;JkhCP0CmQ-HHU;){DUU2Y2ps6TU7bsP(8GX?M zU>WBOikC6M8kT0nG;AqHYf!Aw-txDSCID8cd}xYmMssts#>P1{ykOJHlfrN!BWi7q zMvdf>(0R6CO6`sIqiDTsNbs2GbbGQx;B5kBw^9<7jK+IoWCZsNpgGWAC9u#>vK~p6oN-bD1 z#aV*~P|WyB-EZ_!jC1DX*=r{r#TiHTY%bi9*Fjx3oa;TF zYC5Be1yzNoKJG4>-&B1uy3^GgxAa$#BZ~qen~WgB)yPVfIuc%4Mn)5pm0(h2XrYU; zI~o}>kw{Q7NMvjymnx@4r7NK^7(O@u|JMY64v|G9%3dZ; zR=w~a9f?prPf^K6!R^EpC724p6IBR-$d7;eh%AAK0vL->&&^f$kDRe5mmDwH^!-Cn zbmt>--A?y(E@EW=qtS5~t8iy?F;N$?yNe%R%huhEIsa#7v2Lr-@=3R0X!e~aWw6@^ zUBXYXF z#Pr?dp^Wd22stMYPxNPw-`bKAY8?6Oth2qTg zyv)3Gh3wQy#v2MH86_nJ#a88bl^K%Wupp9g z%QA+5nD7^0+|D{lPdv$Se)2<`Ka7nLk8d*mTEw`JAwV{&r$(m0Cv0+^O}Oy7sAiUh z3{6c78I~~IPz|`7miU2F!-a8LcEr5penv%1$!@;R%nU3HjSCqUGu~v<;9_cQVvJC5 zS!JB{<+R9(N@u;`yQzi8f6YF#etWz}Q>2=1-r^-UrJ1>4CjP%w^I6&0YvRlmY;9@^ zR;QSfd<>Zx5e70bHGvFlcj`Z?)L?7K7FT`F=7ar{QqDBPrw^+4R`K6UIC5U~I7m$x z>$$8_>#L_#+;=2S@2xrQw*0d-^ZUIUbfziX>YH`ImKo@{MGQ9-DjZ-TRghSenpXk~ zD^Nfe>nA5C87C%MnChAtCYk7(7^E5MT9{a*>KYg&rkJH#BpR8TBr$@ldlFsr^}vCx zMYnx>t+v0cH|ziImt<1a(Klp4 zix$?##SEFSc+~?&W)5;BGBq+rIIQ-ZvTmM&YIXEQwcUNWhA#~-zBHP6WYU4R(Q_|z pv~1iI^v1}M@l*EcnmC7|`;R)eZI|7|x6th0q=Mh9OzSHWL;#3(?x6qx diff --git a/cocli/data/corim/templates/corim-cca-realm.json b/cocli/data/corim/templates/corim-cca-realm.json deleted file mode 100644 index f6b07666..00000000 --- a/cocli/data/corim/templates/corim-cca-realm.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", - "profile": "http://arm.com/cca/realm/1", - "validity": { - "not-before": "2021-12-31T00:00:00Z", - "not-after": "2025-12-31T00:00:00Z" - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "acme.example", - "roles": [ - "manifestCreator" - ] - } - ] -} diff --git a/cocli/data/corim/templates/corim-cca.json b/cocli/data/corim/templates/corim-cca.json deleted file mode 100644 index 59e536d5..00000000 --- a/cocli/data/corim/templates/corim-cca.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", - "profile": "http://arm.com/cca/ssd/1", - "validity": { - "not-before": "2021-12-31T00:00:00Z", - "not-after": "2025-12-31T00:00:00Z" - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "acme.example", - "roles": [ - "manifestCreator" - ] - } - ] -} diff --git a/cocli/data/corim/templates/corim-full.json b/cocli/data/corim/templates/corim-full.json deleted file mode 100644 index db4d772a..00000000 --- a/cocli/data/corim/templates/corim-full.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", - "dependent-rims": [ - { - "href": "https://parent.example/rims/ccb3aa85-61b4-40f1-848e-02ad6e8a254b", - "thumbprint": "sha-256:5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" - } - ], - "profile": "http://arm.com/psa/iot/1", - "validity": { - "not-before": "2021-12-31T00:00:00Z", - "not-after": "2025-12-31T00:00:00Z" - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "acme.example", - "roles": [ - "manifestCreator" - ] - } - ] -} diff --git a/cocli/data/corim/templates/corim-mini.json b/cocli/data/corim/templates/corim-mini.json deleted file mode 100644 index bc7e70ba..00000000 --- a/cocli/data/corim/templates/corim-mini.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc" -} diff --git a/cocli/data/corim/templates/meta-full.json b/cocli/data/corim/templates/meta-full.json deleted file mode 100644 index 5c465494..00000000 --- a/cocli/data/corim/templates/meta-full.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "signer": { - "name": "ACME Ltd signing key", - "uri": "https://acme.example" - }, - "validity": { - "not-before": "2021-12-31T00:00:00Z", - "not-after": "2025-12-31T00:00:00Z" - } -} diff --git a/cocli/data/corim/templates/meta-mini.json b/cocli/data/corim/templates/meta-mini.json deleted file mode 100644 index fb978cc8..00000000 --- a/cocli/data/corim/templates/meta-mini.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "signer": { - "name": "ACME Ltd signing key" - } -} diff --git a/cocli/data/corim/unsigned-corim.cbor b/cocli/data/corim/unsigned-corim.cbor deleted file mode 100644 index bcbe320de07ab754f17ab3c84100356a37cd43c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 815 zcmZvYZ%7ki9LMi&(=v*Rd~vDK)1Qe@ceis>hAEXZvv4vpO#A=r_H3SRch_?_om0wP zQEw`V!l)qFW+}a}Aj${=g)++2SAi?+4`>(!2^x_kwXH>OdV75C^Z7mB+l2G*ftmTr z(em6&FP4s9{#g2x)a&2J$%QBxA)MF8d+AzV=GlBu@{)2@^9J&tj-vFW?p%4-vyGi)eCE;i zvC`S?Q-LfGKmDQoNoeJ}<4?y;&y!4D##mEe+v}0dyC1=;7dl2Nd*`+pzCO9PwHv&- zczv*}f$WHrKl+Fu5M)0K1wUdjb{Cp?3nL&#Rd}Yw%J57PV_r^>LGw<#V;9&bLpKgc z62hPYX-Lpy43d8UyWA#g2<&o;nkY-4*2%9xR+eXrGH%gZkcLI*qnVVP<~%|QrmgCK zbab(hNK<-@Ua#$ML?D&5%UF>yOsZ+nm^w9aGrd74sRS2D$7>O85e1}f1$!icrGd}i z_NT*B$vrqlg`6mB9B+&gLpgA)iXew_#QomRjBtz)Q?plVZkC(?zSQ99!>BO%8&X~^ AOaK4? diff --git a/cocli/data/coswid/1.cbor b/cocli/data/coswid/1.cbor deleted file mode 100644 index bc78ffccda80397fa234a5b59fc91a52522bb9ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 377 zcmZvYu}%U(5Qc$(T4v9Ph&?2X64-R)&>4-QM7!Pp}) zv9huA0qA@Jd*ch(N#b#m*w{|K`M-bWDHRbRUB-orOewQkF^;J~8u$hs)Tlx0w6dN` zN0rk?t4S^-H&y6)sK^;=6j~~>*dPr#k{U_Vw6!fg(rFtTSYrmO=N_SiTG`wInuJ67 zMkFxQf`8)RM8xXMcnW*^vWq0qHl*^U;)`e?-K1*V8TQBNOw5H#c?Z?N78o3nb5AfJ zjyw7_u>{U$AK)O`9S(=g@iqyqPJx&J diff --git a/cocli/data/cots/Zesty Hands_ta.ta b/cocli/data/cots/Zesty Hands_ta.ta deleted file mode 100644 index 05705fe16fe8846eb32a51baecaedb99f18a0403..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 698 zcmZ3~#I((ziD{ETq=7ISyH=aWIa?-1HV!a@nUS5@iGgMM`P_o*Sq2U~l^CmS6`MPIp6jcv>y7c#*e=1>+k9;VP>Ln#9Z5QmFLFe(rw~$9T3n*wn3tT9U$mf!@u@)*WA_4PCPpSE z5$-!)3j!+x>>@suUJY_9cHgsptFZwW)PpR{Otf%^_4@kzt1V<_8N`FADUs coboI0*Zgb81a?e_-*?mgbWlL4_ls2#0F{=a+uKsDR-m6OEv#aTqUTF(D$n7Vk6*|m2|C9X~Hoqb(C0cdHA zL5v}{0Vf-CC<_}8Q)sZEyn!r;!^I;OoR^=Jlv$ji;8;+QSd^Nhqu`mBtY@fWpaoLE z%%hB5fkH@8X>o~yV_tGbe$j#^#$N_ajPn;TGchtTi3mtfc1b-T;OZhLz2oTe1M^ny zTiId21@$TmGZSqcV<0EaYh+|#YG`R_YG`a=5hc!R4C0zWxiq$&cCowI-oO?dZL)kU zVmP9j8yIP_{4BsgW@<6u2XTZM8UM4e8ZZMXY-1U{=V~%sar5AwYa2G!6Pv*r5Kv9)eTf330n?B4n0jj1*Yk4Xqx7} z$e&shbJwZfR>WYQB$&c6iw{Joqj2eKeY6VH8P1$za9(* zu1pHDOS_-{a?#1T;rZS)UCC<_^C4By&a_QZe;qzc>U?&e0ZhI#)Hgp^xO!2bm*tdS zdB5ggJ0`GWLj1m)_NRjaO1)pKiU1|wI$-jx0;Z%gJjwTQ;3@%274{{2nQIp85e&KT zobf}tzvXEi|49OwIcb&lWGbisfsdOm^)#=ZBWed;;Tw;%dgH;+Px$iwfv>fK7#3zQuA@AK}sV5Ee&Fg z5#rOJJA;7>lL9xJx&zzsmuGTSefGPwU;D-A_G-Ri-K0jzJnomve!u<7q!89$kpL=#juLUpsv+*$MDXLt%!St%~tN+1GOcBv|Qr_Aw9>!bqZZQSl(LPk9e)G7Z z?T`DyFXrq#yM4uzZMt(_temA=5W`>Ja69z?+eJ$ diff --git a/cocli/data/cots/namedtastore.cbor b/cocli/data/cots/namedtastore.cbor deleted file mode 100644 index 2bbbcc174136a5fc0abcfddf0f54a576907cc1dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 766 zcmZ3))VPqj%r~<*IW;FIF)ua0v{)g;Q6ab_zbKV$Awy#mVL~jD$b=w`Ee`K6qD)!gR$LI6K|97t+sAjvqa+0{dIEzSE%lV%TQy1?syY_CW z#I@ZQ4CKUljf@OT4J{2#4UG*fqQrTPL0mH^m&Uf! zE_N5&8`y%QO_q;E3`bOR10zkAp9L7mOf3fdAdWC2<9`-b17;wF90$yv3+hZX$L?>HcnbZR+Qs)yIpduF{>_e4KqZZ9xqtogNNY7*{QHb;%e00( U#gextv-ikz)+MBeyY`m@0M@k!{{R30 diff --git a/cocli/data/cots/rubbish.cbor b/cocli/data/cots/rubbish.cbor deleted file mode 100644 index 336db72d08713f4f738746202ae0aabe61c3bb8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1078 zcmaFAe=pPf{d<9kxvaPX0u@3W6@p9hi&EKu!VC~u#z>}{i-1g!Vgnc*X&}tTt_9?I z0I@9-BO3>t!_3Ie?8LwV6o;S`Af>}{b~2F41>!P@D4f)TurhQYv<#49;e@ccvPwX* zd_Zgq5do4JrTjv_ciE|kl8JHSc8k!m!8(2h%a~XrUW>7ATZ6E9j3>Y`|2Dab?BFo1jMrwNF z2BsTXeimRXg0mGrh%3wpN)#-t2FzdvdUP{;G8niqDS~okArPC_7oVS^3}ir1K9F(; zVkMqJKZuApgaVSzAV~;<=m)a7A$*TCAZ-X?14&yrD^IZm!p;U#@|<-E>EW(GRyhFI C(GRTv diff --git a/cocli/data/cots/templates/claims/exclclaim.json b/cocli/data/cots/templates/claims/exclclaim.json deleted file mode 100644 index 409e138c..00000000 --- a/cocli/data/cots/templates/claims/exclclaim.json +++ /dev/null @@ -1 +0,0 @@ -{"swname":"More Bitter Paper"} \ No newline at end of file diff --git a/cocli/data/cots/templates/claims/permclaim.json b/cocli/data/cots/templates/claims/permclaim.json deleted file mode 100644 index e5f304cd..00000000 --- a/cocli/data/cots/templates/claims/permclaim.json +++ /dev/null @@ -1 +0,0 @@ -{"swname":"Bitter Paper"} \ No newline at end of file diff --git a/cocli/data/cots/templates/env/comid.json b/cocli/data/cots/templates/env/comid.json deleted file mode 100644 index d692c138..00000000 --- a/cocli/data/cots/templates/env/comid.json +++ /dev/null @@ -1 +0,0 @@ -[{"environment":{"class":{"id":{"type":"oid","value":"1.2.3.4.5"}}}}] \ No newline at end of file diff --git a/cocli/data/cots/templates/env/coswid.json b/cocli/data/cots/templates/env/coswid.json deleted file mode 100644 index a051b8e7..00000000 --- a/cocli/data/cots/templates/env/coswid.json +++ /dev/null @@ -1 +0,0 @@ -[{"swidtag":{"entity":[{"entity-name":"Zesty Hands, Inc.","role":"softwareCreator"}]}}] \ No newline at end of file diff --git a/cocli/data/cots/templates/env/namedtastore.json b/cocli/data/cots/templates/env/namedtastore.json deleted file mode 100644 index 24e88271..00000000 --- a/cocli/data/cots/templates/env/namedtastore.json +++ /dev/null @@ -1 +0,0 @@ -[{"namedtastore":"Miscellaneous TA Store"}] \ No newline at end of file diff --git a/cocli/data/cots/templates/env/vendor.json b/cocli/data/cots/templates/env/vendor.json deleted file mode 100644 index 571df036..00000000 --- a/cocli/data/cots/templates/env/vendor.json +++ /dev/null @@ -1 +0,0 @@ -[{"environment":{"class":{"vendor":"Zesty Hands, Inc."}}}] \ No newline at end of file diff --git a/cocli/data/cots/templates/env/vendor2.json b/cocli/data/cots/templates/env/vendor2.json deleted file mode 100644 index ae4f2027..00000000 --- a/cocli/data/cots/templates/env/vendor2.json +++ /dev/null @@ -1 +0,0 @@ -[{"environment":{"class":{"vendor":"Worthless Sea, Inc."}}}] \ No newline at end of file diff --git a/cocli/data/cots/templates/env/vendors.json b/cocli/data/cots/templates/env/vendors.json deleted file mode 100644 index f8160924..00000000 --- a/cocli/data/cots/templates/env/vendors.json +++ /dev/null @@ -1 +0,0 @@ -[{"environment":{"class":{"vendor":"Zesty Hands, Inc."}}},{"environment":{"class":{"vendor":"Snobbish Apparel, Inc."}}}] \ No newline at end of file diff --git a/cocli/data/cots/vendor.cbor b/cocli/data/cots/vendor.cbor deleted file mode 100644 index 8996a35a32efaf6d3a32955c4a071879b5de8064..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 674 zcmZ3))VPpwA;Ut(!l=~Zl1c@S#JrSZ9R<(4WIeWp42?~UkxX@qnwY8#nwZKAA`OJu z*tOa`&e<|CvT=YJ%#7^JP7ExM16K)Hs<1EF%UrWyk6_4!=ZqiH{Vh-H_)ikZ)Oo${ zjja0g+M|i7rE+}A)qj7Bn!TLW;X2W2TH<}v^9S~82b^IMVT^G-xn_f#R$=UQfdv%@ zL_I$;+5;_RG_W(|HsEAq4rO8EVG0d46foceakzNcT`LlE3vyBoWeuc3BFsD@P!WZY zqSE3L1;@PPjQpYnO^kaDnix|SFf%bSF^Mu*d> z21)a?00WAt#eg5g5oTok&%$cJ45X05k=dQWz=cVHn@!z;?fA_~{qhucU%Vodceq~Y!YqZM~Nk%o+WxpN{9?|| vv)flZ*`_<^#mZT_1u^{f4YyMduwCR7QQA^}Oz`xTjLc%&IUW`*SN{P3*O4YG diff --git a/cocli/data/keys/ec-p256.jwk b/cocli/data/keys/ec-p256.jwk deleted file mode 100644 index e3c07719..00000000 --- a/cocli/data/keys/ec-p256.jwk +++ /dev/null @@ -1,9 +0,0 @@ -{ - "kty": "EC", - "crv": "P-256", - "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", - "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", - "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", - "use": "enc", - "kid": "1" -} diff --git a/cocli/data/pics/cocli-map.graffle b/cocli/data/pics/cocli-map.graffle deleted file mode 100644 index fc669abf193135cea4443905d589c91d1f3c509a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 171894 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%0A10LrC-5=-ZssF@z#{n` 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 diff --git a/cocli/data/pics/cocli-map.png b/cocli/data/pics/cocli-map.png deleted file mode 100644 index bca1faa9b2c5fe031d339e034ad838d05847c063..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110025 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≫loYU!+$ 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

trN`6r1G;sXuCr?RGU3O`NUDBG2 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 diff --git a/cocli/main.go b/cocli/main.go deleted file mode 100644 index b319cc3d..00000000 --- a/cocli/main.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2021-2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "github.com/veraison/corim/cocli/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/cocli/data/cots/Snobbish Apparel_ta.ta b/cots/data/Snobbish Apparel_ta.ta similarity index 100% rename from cocli/data/cots/Snobbish Apparel_ta.ta rename to cots/data/Snobbish Apparel_ta.ta diff --git a/cocli/data/cots/shared_ta.ta b/cots/data/shared_ta.ta similarity index 100% rename from cocli/data/cots/shared_ta.ta rename to cots/data/shared_ta.ta diff --git a/cots/example_test.go b/cots/example_test.go index 589f79c4..48add10e 100644 --- a/cots/example_test.go +++ b/cots/example_test.go @@ -173,8 +173,8 @@ func Example_decode_CBOR() { } func Example_list_of_cots_roundtrip() { - snobTa, _ := os.ReadFile("../cocli/data/cots/Snobbish Apparel_ta.ta") - sharedTa, _ := os.ReadFile("../cocli/data/cots/shared_ta.ta") + snobTa, _ := os.ReadFile("./data/Snobbish Apparel_ta.ta") + sharedTa, _ := os.ReadFile("./data/shared_ta.ta") // cts1 egSnob := NewEnvironmentGroup() diff --git a/scripts/licenses.sh b/scripts/licenses.sh index accdad92..68fff931 100755 --- a/scripts/licenses.sh +++ b/scripts/licenses.sh @@ -8,7 +8,6 @@ type go-licenses &> /dev/null || go get github.com/google/go-licenses MODULES+=("github.com/veraison/corim/corim") MODULES+=("github.com/veraison/corim/comid") -MODULES+=("github.com/veraison/corim/cocli") for module in ${MODULES[@]} do From 4830705ae54ec4b803c314f9914968ae5a68ff36 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Wed, 11 Sep 2024 17:49:34 +0200 Subject: [PATCH 051/110] chore(deps): update go-cose to v1.2.1 Signed-off-by: Thomas Fossati --- comid/cryptokey_test.go | 2 +- corim/signer.go | 2 +- go.mod | 28 +- go.sum | 701 +--------------------------------------- 4 files changed, 7 insertions(+), 726 deletions(-) diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index d898d48e..11f05ade 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -128,7 +128,7 @@ func Test_CryptoKey_NewCOSEKey(t *testing.T) { 0x03, 0x41, 0x01, // alg: bstr(1) } _, err = NewCOSEKey(badKey) - assert.Contains(t, err.Error(), "alg: invalid type") + assert.Contains(t, err.Error(), "invalid algorithm value") keySet, err := NewCOSEKey(TestCOSEKeySetOne) require.NoError(t, err) diff --git a/corim/signer.go b/corim/signer.go index 62e104fb..cf971292 100644 --- a/corim/signer.go +++ b/corim/signer.go @@ -141,7 +141,7 @@ func getAlgAndKeyFromJWK(j []byte) (cose.Algorithm, crypto.Signer, error) { return noAlg, nil, fmt.Errorf("unknown elliptic curve %v", crv) } case ed25519.PrivateKey: - alg = cose.AlgorithmEdDSA + alg = cose.AlgorithmEd25519 case *rsa.PrivateKey: alg = rsaJWKToAlg(k) if alg == noAlg { diff --git a/go.mod b/go.mod index d5d7f8ce..1e3a1cfc 100644 --- a/go.mod +++ b/go.mod @@ -4,50 +4,28 @@ go 1.22 require ( github.com/fxamacker/cbor/v2 v2.5.0 - github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.0 github.com/lestrrat-go/jwx/v2 v2.0.8 - github.com/spf13/afero v1.9.2 github.com/spf13/cast v1.4.1 - github.com/spf13/cobra v1.2.1 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.8.2 - github.com/veraison/apiclient v0.3.1-0.20240807160142-9141ad363e45 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff - github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7 + github.com/veraison/go-cose v1.2.1 github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/goccy/go-json v0.9.11 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/kr/pretty v0.2.0 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.4 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.0 // indirect - github.com/magiconair/properties v1.8.5 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moogar0880/problems v0.1.1 // indirect - github.com/pelletier/go-toml v1.9.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/subosito/gotenv v1.2.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/ini.v1 v1.63.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d90ae3a0..991148ed 100644 --- a/go.sum +++ b/go.sum @@ -1,226 +1,17 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -238,529 +29,41 @@ github.com/lestrrat-go/jwx/v2 v2.0.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9H github.com/lestrrat-go/jwx/v2 v2.0.8/go.mod h1:zLxnyv9rTlEvOUHbc48FAfIL8iYu2hHvIRaTFGc8mT0= github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/moogar0880/problems v0.1.1 h1:bktLhq8NDG/czU2ZziYNigBFksx13RaYe5AVdNmHDT4= -github.com/moogar0880/problems v0.1.1/go.mod h1:5Dxrk2sD7BfBAgnOzQ1yaTiuCYdGPUh49L8Vhfky62c= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= -github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= -github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/veraison/apiclient v0.3.1-0.20240807160142-9141ad363e45 h1:o+gCzGtusXZOOXuJavzvpraSk8ify4U60Aq9r/4vOho= -github.com/veraison/apiclient v0.3.1-0.20240807160142-9141ad363e45/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff h1:r6I2eJL/z8dp5flsQIKHMeDjyV6UO8If3MaVBLvTjF4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= -github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7 h1:KcKzBthSrSZIUEWBjVvkuk/DE3PyYFbXZxhx5byGFtc= -github.com/veraison/go-cose v1.1.1-0.20230825153510-da0f9a62ade7/go.mod h1:t6V8WJzHm1PD5HNsuDjW3KLv577uWb6UTzbZGvdQHD8= +github.com/veraison/go-cose v1.2.1 h1:Gj4x20D0YP79J2+cK3anjGEMwIkg2xX+TKVVGUXwNAc= +github.com/veraison/go-cose v1.2.1/go.mod h1:t6V8WJzHm1PD5HNsuDjW3KLv577uWb6UTzbZGvdQHD8= github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca h1:osmCKwWO/xM68Kz+rIXio1DNzEY2NdJOpGpoy5r8NlE= github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca/go.mod h1:d5jt76uMNbTfQ+f2qU4Lt8RvWOTsv6PFgstIM1QdMH0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= -gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From e8e6b64f202c6c9326f5d0a595e4875318ae2f47 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 3 Oct 2024 09:53:45 +0100 Subject: [PATCH 052/110] fix(comid)!: Revert "fix(comid): bring ValueTriple into alignment with rev05" Revert the fix applied for aligning Value Triples with rev05 This reverts commit be7ec48294794682efe017eeae95ea00f6aeac56. --- comid/comid_test.go | 18 +- comid/example_cca_realm_refval_test.go | 20 +- comid/example_cca_refval_test.go | 73 +++-- comid/example_psa_refval_test.go | 46 ++- comid/example_test.go | 288 +++++++++++++----- comid/measurement.go | 45 +++ comid/test_vars.go | 281 +++++++---------- comid/testcases/comid-1.cbor | Bin 174 -> 0 bytes comid/testcases/comid-2.cbor | Bin 450 -> 0 bytes comid/testcases/comid-3.cbor | Bin 151 -> 0 bytes comid/testcases/comid-design-cd.cbor | Bin 607 -> 0 bytes comid/testcases/comid-firmware-cd.cbor | Bin 339 -> 0 bytes comid/testcases/regen-from-src.sh | 21 -- comid/testcases/src/comid-1.diag | 37 --- comid/testcases/src/comid-2.diag | 97 ------ comid/testcases/src/comid-3.diag | 34 --- comid/testcases/src/comid-design-cd.diag | 119 -------- comid/testcases/src/comid-firmware-cd.diag | 80 ----- comid/valuetriple.go | 29 +- comid/valuetriple_test.go | 2 +- corim/example_profile_test.go | 27 +- corim/profiles_test.go | 4 +- corim/testcases/comid-ext.json | 28 +- corim/testcases/comid.json | 26 +- .../signed-corim-with-extensions.cbor | Bin 800 -> 681 bytes corim/testcases/signed-example-corim.cbor | Bin 803 -> 684 bytes corim/testcases/signed-good-corim.cbor | Bin 726 -> 607 bytes .../testcases/src/corim-with-extensions.yaml | 92 +++--- corim/testcases/src/example-corim.yaml | 92 +++--- corim/testcases/src/good-corim.yaml | 86 +++--- .../unsigned-corim-with-extensions.cbor | Bin 633 -> 514 bytes corim/testcases/unsigned-example-corim.cbor | Bin 636 -> 517 bytes corim/testcases/unsigned-good-corim.cbor | Bin 559 -> 440 bytes 33 files changed, 646 insertions(+), 899 deletions(-) delete mode 100644 comid/testcases/comid-1.cbor delete mode 100644 comid/testcases/comid-2.cbor delete mode 100644 comid/testcases/comid-3.cbor delete mode 100644 comid/testcases/comid-design-cd.cbor delete mode 100644 comid/testcases/comid-firmware-cd.cbor delete mode 100644 comid/testcases/regen-from-src.sh delete mode 100644 comid/testcases/src/comid-1.diag delete mode 100644 comid/testcases/src/comid-2.diag delete mode 100644 comid/testcases/src/comid-3.diag delete mode 100644 comid/testcases/src/comid-design-cd.diag delete mode 100644 comid/testcases/src/comid-firmware-cd.diag diff --git a/comid/comid_test.go b/comid/comid_test.go index a708b115..7aed60dd 100644 --- a/comid/comid_test.go +++ b/comid/comid_test.go @@ -48,11 +48,11 @@ func Test_Comid_ToJSONPretty(t *testing.T) { Environment: Environment{ Instance: MustNewUUIDInstance(TestUUID), }, - Measurement: Measurement{ + Measurements: *NewMeasurements().Add(&Measurement{ Val: Mval{ RawValue: NewRawValue().SetBytes(MustHexDecode(t, "deadbeef")), }, - }, + }), }), } @@ -70,14 +70,16 @@ func Test_Comid_ToJSONPretty(t *testing.T) { "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" } }, - "measurement": { - "value": { - "raw-value": { - "type": "bytes", - "value": "3q2+7w==" + "measurements": [ + { + "value": { + "raw-value": { + "type": "bytes", + "value": "3q2+7w==" + } } } - } + ] } ] } diff --git a/comid/example_cca_realm_refval_test.go b/comid/example_cca_realm_refval_test.go index fa17fb74..b2965eb7 100644 --- a/comid/example_cca_realm_refval_test.go +++ b/comid/example_cca_realm_refval_test.go @@ -71,8 +71,24 @@ func extractRealmRefVal(rv ValueTriple) error { return fmt.Errorf("extracting realm instanceID: %w", err) } - if err := extractMeasurement(rv.Measurement); err != nil { - return fmt.Errorf("extracting measurement: %w", err) + measurements := rv.Measurements + + if err := extractMeasurements(measurements); err != nil { + return fmt.Errorf("extracting measurements: %w", err) + } + + return nil +} + +func extractMeasurements(m Measurements) error { + if len(m.Values) == 0 { + return fmt.Errorf("no measurements") + } + + for i, meas := range m.Values { + if err := extractMeasurement(meas); err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } } return nil diff --git a/comid/example_cca_refval_test.go b/comid/example_cca_refval_test.go index 1e6787b7..92d0d49d 100644 --- a/comid/example_cca_refval_test.go +++ b/comid/example_cca_refval_test.go @@ -21,24 +21,21 @@ func Example_cca_refval() { } // output: - //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - //Label: BL - //Version: 2.1.0 - //Digest: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 - //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - //Label: PRoT - //Version: 1.3.5 - //Digest: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f - //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - //Label: ARoT - //Version: 0.1.4 - //Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 - //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - //Label: a non-empty (unique) label - //Raw value: 72617776616c75650a72617776616c75650a + // ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + // Label: BL + // Version: 2.1.0 + // Digest: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 + // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + // Label: PRoT + // Version: 1.3.5 + // Digest: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f + // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + // Label: ARoT + // Version: 0.1.4 + // Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 + // Label: a non-empty (unique) label + // Raw value: 72617776616c75650a72617776616c75650a } func extractCcaRefVals(c *Comid) error { @@ -57,33 +54,35 @@ func extractCcaRefVals(c *Comid) error { func extractCCARefVal(rv ValueTriple) error { class := rv.Environment.Class - m := rv.Measurement if err := extractImplementationID(class); err != nil { return fmt.Errorf("extracting impl-id: %w", err) } - if m.Key == nil { - return fmt.Errorf("missing mKey") - } - if !m.Key.IsSet() { - return fmt.Errorf("mKey not set") - } - - switch t := m.Key.Value.(type) { - case *TaggedPSARefValID: - if err := extractSwMeasurement(m); err != nil { - return fmt.Errorf("extracting measurement: %w", err) + for i, m := range rv.Measurements.Values { + if m.Key == nil { + return fmt.Errorf("missing mKey at index %d", i) } - case *TaggedCCAPlatformConfigID: - if err := extractCCARefValID(m.Key); err != nil { - return fmt.Errorf("extracting cca-refval-id: %w", err) + if !m.Key.IsSet() { + return fmt.Errorf("mKey not set at index %d", i) } - if err := extractRawValue(m.Val.RawValue); err != nil { - return fmt.Errorf("extracting raw vlue: %w", err) + + switch t := m.Key.Value.(type) { + case *TaggedPSARefValID: + if err := extractSwMeasurement(m); err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + case *TaggedCCAPlatformConfigID: + if err := extractCCARefValID(m.Key); err != nil { + return fmt.Errorf("extracting cca-refval-id: %w", err) + } + if err := extractRawValue(m.Val.RawValue); err != nil { + return fmt.Errorf("extracting raw vlue: %w", err) + } + default: + return fmt.Errorf("unexpected Mkey type: %T", t) } - default: - return fmt.Errorf("unexpected Mkey type: %T", t) + } return nil diff --git a/comid/example_psa_refval_test.go b/comid/example_psa_refval_test.go index 1d7c7bb0..7fa78313 100644 --- a/comid/example_psa_refval_test.go +++ b/comid/example_psa_refval_test.go @@ -21,21 +21,19 @@ func Example_psa_refval() { } // output: - //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - //Label: BL - //Version: 2.1.0 - //Digest: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 - //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - //Label: PRoT - //Version: 1.3.5 - //Digest: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f - //ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 - //SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b - //Label: ARoT - //Version: 0.1.4 - //Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 + // ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 + // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + // Label: BL + // Version: 2.1.0 + // Digest: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 + // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + // Label: PRoT + // Version: 1.3.5 + // Digest: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f + // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b + // Label: ARoT + // Version: 0.1.4 + // Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 } func extractRefVals(c *Comid) error { @@ -59,13 +57,29 @@ func extractPSARefVal(rv ValueTriple) error { return fmt.Errorf("extracting impl-id: %w", err) } - if err := extractSwMeasurement(rv.Measurement); err != nil { + measurements := rv.Measurements + + if err := extractSwMeasurements(measurements); err != nil { return fmt.Errorf("extracting measurements: %w", err) } return nil } +func extractSwMeasurements(m Measurements) error { + if len(m.Values) == 0 { + return fmt.Errorf("no measurements") + } + + for i, m := range m.Values { + if err := extractSwMeasurement(m); err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + } + + return nil +} + func extractSwMeasurement(m Measurement) error { if err := extractPSARefValID(m.Key); err != nil { return fmt.Errorf("extracting PSA refval id: %w", err) diff --git a/comid/example_test.go b/comid/example_test.go index ce9a36a5..d94b8fa8 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -4,7 +4,6 @@ package comid import ( - _ "embed" "fmt" "github.com/google/uuid" @@ -30,18 +29,21 @@ func Example_encode() { Instance: MustNewUEIDInstance(TestUEID), Group: MustNewUUIDGroup(TestUUID), }, - Measurement: *MustNewUUIDMeasurement(TestUUID). - SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). - SetSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). - SetFlagsTrue(FlagIsDebug). - SetFlagsFalse(FlagIsSecure). - SetSerialNumber("C02X70VHJHD5"). - SetUEID(TestUEID). - SetUUID(TestUUID). - SetMACaddr(MACaddr(TestMACaddr)). - SetIPaddr(TestIPaddr), + Measurements: *NewMeasurements(). + Add( + MustNewUUIDMeasurement(TestUUID). + SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). + SetSVN(2). + AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + SetFlagsTrue(FlagIsDebug). + SetFlagsFalse(FlagIsSecure). + SetSerialNumber("C02X70VHJHD5"). + SetUEID(TestUEID). + SetUUID(TestUUID). + SetMACaddr(MACaddr(TestMACaddr)). + SetIPaddr(TestIPaddr), + ), }, ). AddEndorsedValue( @@ -55,18 +57,21 @@ func Example_encode() { Instance: MustNewUEIDInstance(TestUEID), Group: MustNewUUIDGroup(TestUUID), }, - Measurement: *MustNewUUIDMeasurement(TestUUID). - SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). - SetMinSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). - SetFlagsTrue(FlagIsDebug). - SetFlagsFalse(FlagIsSecure, FlagIsConfigured). - SetSerialNumber("C02X70VHJHD5"). - SetUEID(TestUEID). - SetUUID(TestUUID). - SetMACaddr(MACaddr(TestMACaddr)). - SetIPaddr(TestIPaddr), + Measurements: *NewMeasurements(). + Add( + MustNewUUIDMeasurement(TestUUID). + SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). + SetMinSVN(2). + AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + SetFlagsTrue(FlagIsDebug). + SetFlagsFalse(FlagIsSecure, FlagIsConfigured). + SetSerialNumber("C02X70VHJHD5"). + SetUEID(TestUEID). + SetUUID(TestUUID). + SetMACaddr(MACaddr(TestMACaddr)). + SetIPaddr(TestIPaddr), + ), }, ). AddAttestVerifKey( @@ -102,8 +107,8 @@ func Example_encode() { } // Output: - //a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfaa200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfaa200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurement":{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurement":{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + //a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA() { @@ -117,23 +122,19 @@ func Example_encode_PSA() { SetVendor("ACME Ltd."). SetModel("RoadRunner 2.0"), }, - Measurement: *MustNewPSAMeasurement( - MustCreatePSARefValID( - TestSignerID, "BL", "5.0.5", - )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), - }, - ). - AddReferenceValue( - ValueTriple{ - Environment: Environment{ - Class: NewClassImplID(TestImplID). - SetVendor("ACME Ltd."). - SetModel("RoadRunner 2.0"), - }, - Measurement: *MustNewPSAMeasurement( - MustCreatePSARefValID( - TestSignerID, "PRoT", "1.3.5", - )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), + Measurements: *NewMeasurements(). + Add( + MustNewPSAMeasurement( + MustCreatePSARefValID( + TestSignerID, "BL", "5.0.5", + )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), + ). + Add( + MustNewPSAMeasurement( + MustCreatePSARefValID( + TestSignerID, "PRoT", "1.3.5", + )).AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}), + ), }, ). AddAttestVerifKey( @@ -159,8 +160,8 @@ func Example_encode_PSA() { } // Output: - //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008282a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e30a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef0082a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e30a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurement":{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}},{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurement":{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA_attestation_verification() { @@ -380,29 +381,28 @@ func Example_decode_JSON() { // Output: OK } -var ( - // test cases are based on diag files here: - // https://github.com/ietf-rats-wg/draft-ietf-rats-corim/tree/main/cddl/examples - - //go:embed testcases/comid-1.cbor - testComid1 []byte - - //go:embed testcases/comid-2.cbor - testComid2 []byte - - //go:embed testcases/comid-design-cd.cbor - testComidDesignCD []byte - - //go:embed testcases/comid-firmware-cd.cbor - testComidFirmwareCD []byte - - //go:embed testcases/comid-3.cbor - testComid3 []byte -) - func Example_decode_CBOR_1() { + // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-1.diag + in := []byte{ + 0xa3, 0x01, 0xa1, 0x00, 0x50, 0x3f, 0x06, 0xaf, 0x63, 0xa9, 0x3c, 0x11, + 0xe4, 0x97, 0x97, 0x00, 0x50, 0x56, 0x90, 0x77, 0x3f, 0x02, 0x81, 0xa3, + 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, + 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, + 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, + 0x81, 0x00, 0x04, 0xa1, 0x00, 0x81, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, + 0x25, 0x50, 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, + 0xab, 0x5b, 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x6f, 0x41, 0x43, 0x4d, 0x45, 0x20, + 0x52, 0x6f, 0x61, 0x64, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x03, 0x01, + 0x81, 0xa1, 0x01, 0xa2, 0x00, 0xa2, 0x00, 0x65, 0x31, 0x2e, 0x30, 0x2e, + 0x30, 0x01, 0x19, 0x40, 0x00, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x44, + 0xaa, 0x33, 0x6a, 0xf4, 0xcb, 0x14, 0xa8, 0x79, 0x43, 0x2e, 0x53, 0xdd, + 0x65, 0x71, 0xc7, 0xfa, 0x9b, 0xcc, 0xaf, 0xb7, 0x5f, 0x48, 0x82, 0x59, + 0x26, 0x2d, 0x6e, 0xa3, 0xa4, 0xd9, 0x1b, + } + comid := Comid{} - err := comid.FromCBOR(testComid1) + err := comid.FromCBOR(in) if err != nil { fmt.Printf("FAIL: %v", err) } else { @@ -413,8 +413,50 @@ func Example_decode_CBOR_1() { } func Example_decode_CBOR_2() { + // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-2.diag + in := []byte{ + 0xa3, 0x01, 0xa1, 0x00, 0x50, 0x3f, 0x06, 0xaf, 0x63, 0xa9, 0x3c, 0x11, + 0xe4, 0x97, 0x97, 0x00, 0x50, 0x56, 0x90, 0x77, 0x3f, 0x02, 0x81, 0xa3, + 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, + 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, + 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, + 0x81, 0x00, 0x04, 0xa2, 0x00, 0x83, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, + 0x25, 0x50, 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, + 0xab, 0x5b, 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x78, 0x18, 0x41, 0x43, 0x4d, 0x45, + 0x20, 0x52, 0x6f, 0x61, 0x64, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x20, + 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x03, 0x01, 0x81, 0xa1, + 0x01, 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x44, 0xaa, 0x33, 0x6a, + 0xf4, 0xcb, 0x14, 0xa8, 0x79, 0x43, 0x2e, 0x53, 0xdd, 0x65, 0x71, 0xc7, + 0xfa, 0x9b, 0xcc, 0xaf, 0xb7, 0x5f, 0x48, 0x82, 0x59, 0x26, 0x2d, 0x6e, + 0xa3, 0xa4, 0xd9, 0x1b, 0x82, 0xa1, 0x00, 0xa5, 0x00, 0xd8, 0x25, 0x50, + 0xa7, 0x1b, 0x3e, 0x38, 0x8d, 0x45, 0x4a, 0x05, 0x81, 0xf3, 0x52, 0xe5, + 0x8c, 0x83, 0x2c, 0x5c, 0x01, 0x6a, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x77, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, + 0x43, 0x6f, 0x79, 0x6f, 0x74, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x65, 0x64, 0x20, 0x4f, 0x53, 0x03, 0x02, 0x04, 0x00, 0x81, 0xa1, 0x01, + 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0xbb, 0x71, 0x19, 0x8e, 0xd6, + 0x0a, 0x95, 0xdc, 0x3c, 0x61, 0x9e, 0x55, 0x5c, 0x2c, 0x0b, 0x8d, 0x75, + 0x64, 0xa3, 0x80, 0x31, 0xb0, 0x34, 0xa1, 0x95, 0x89, 0x25, 0x91, 0xc6, + 0x53, 0x65, 0xb0, 0x82, 0xa1, 0x00, 0xa5, 0x00, 0xd8, 0x25, 0x50, 0xa7, + 0x1b, 0x3e, 0x38, 0x8d, 0x45, 0x4a, 0x05, 0x81, 0xf3, 0x52, 0xe5, 0x8c, + 0x83, 0x2c, 0x5c, 0x01, 0x6a, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x02, 0x77, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, 0x43, + 0x6f, 0x79, 0x6f, 0x74, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, + 0x64, 0x20, 0x4f, 0x53, 0x03, 0x02, 0x04, 0x01, 0x81, 0xa1, 0x01, 0xa1, + 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0xbb, 0x71, 0x19, 0x8e, 0xd6, 0x0a, + 0x95, 0xdc, 0x3c, 0x61, 0x9e, 0x55, 0x5c, 0x2c, 0x0b, 0x8d, 0x75, 0x64, + 0xa3, 0x80, 0x31, 0xb0, 0x34, 0xa1, 0x95, 0x89, 0x25, 0x91, 0xc6, 0x53, + 0x65, 0xb0, 0x01, 0x81, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, 0x25, 0x50, + 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, 0xab, 0x5b, + 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x02, 0x72, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x52, 0x6f, + 0x6f, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x03, + 0x00, 0x81, 0xa1, 0x01, 0xa1, 0x01, 0xd9, 0x02, 0x28, 0x01, + } + comid := Comid{} - err := comid.FromCBOR(testComid2) + err := comid.FromCBOR(in) if err != nil { fmt.Printf("FAIL: %v", err) } else { @@ -425,8 +467,63 @@ func Example_decode_CBOR_2() { } func Example_decode_CBOR_3() { + // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-design-cd.diag + in := []byte{ + 0xa4, 0x01, 0xa1, 0x00, 0x50, 0x1e, 0xac, 0xd5, 0x96, 0xf4, 0xa3, 0x4f, + 0xb6, 0x99, 0xbf, 0xae, 0xb5, 0x8e, 0x0a, 0x4e, 0x47, 0x02, 0x81, 0xa3, + 0x00, 0x71, 0x46, 0x50, 0x47, 0x41, 0x20, 0x44, 0x65, 0x73, 0x69, 0x67, + 0x6e, 0x73, 0x2d, 0x52, 0x2d, 0x55, 0x73, 0x01, 0xd8, 0x20, 0x78, 0x1e, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x70, 0x67, 0x61, + 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x81, 0x00, 0x03, 0x81, 0xa2, + 0x00, 0x50, 0x97, 0xf5, 0xa7, 0x07, 0x1c, 0x6f, 0x43, 0x8f, 0x87, 0x7a, + 0x4a, 0x02, 0x07, 0x80, 0xeb, 0xe9, 0x01, 0x00, 0x04, 0xa2, 0x00, 0x84, + 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, + 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x01, 0x01, 0x76, 0x66, 0x70, 0x67, + 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, + 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, + 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x02, 0x01, 0x76, 0x66, 0x70, 0x67, + 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, + 0xa1, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x3f, 0xe1, 0x8e, 0xca, 0x40, + 0x53, 0x87, 0x9e, 0x01, 0x7e, 0xf5, 0xeb, 0x7a, 0x3e, 0x51, 0x57, 0x65, + 0x9c, 0x5f, 0x9b, 0xb1, 0x5b, 0x7d, 0x09, 0x95, 0x9b, 0x8b, 0x86, 0x47, + 0x82, 0x2a, 0x4c, 0xc2, 0x1c, 0x3a, 0xa6, 0x72, 0x1c, 0xef, 0x87, 0xf5, + 0xbf, 0xa5, 0x34, 0x95, 0xdb, 0x08, 0x33, 0x82, 0xa1, 0x00, 0xa3, 0x00, + 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, + 0x04, 0x03, 0x01, 0x76, 0x66, 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, + 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, 0xa1, 0x02, 0x81, 0x82, 0x07, + 0x58, 0x30, 0x20, 0xff, 0x68, 0x1a, 0x08, 0x82, 0xe2, 0x9b, 0x48, 0x19, + 0x53, 0x88, 0x89, 0x36, 0x20, 0x9c, 0xb5, 0x3d, 0xf9, 0xc5, 0xaa, 0xec, + 0x60, 0x6a, 0x2c, 0x24, 0xa0, 0xfb, 0x13, 0x85, 0x95, 0x12, 0x4b, 0x8e, + 0x3f, 0x24, 0xa1, 0x27, 0x71, 0xbc, 0x38, 0x54, 0xcc, 0x68, 0xb4, 0x03, + 0x61, 0xad, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, + 0x48, 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x01, 0x01, 0x76, + 0x66, 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, + 0x75, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x81, 0xa1, + 0x01, 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x58, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x58, 0x30, 0x46, + 0x62, 0x24, 0x34, 0x3d, 0x68, 0x18, 0x02, 0xc1, 0x50, 0x6b, 0xbe, 0xd7, + 0xd7, 0xf0, 0x0b, 0x96, 0x9b, 0xad, 0xdd, 0x63, 0x46, 0xe4, 0xf2, 0xe7, + 0xce, 0x14, 0x66, 0x92, 0x99, 0x6f, 0x22, 0xa4, 0x58, 0x14, 0xde, 0x81, + 0xd2, 0x48, 0xf5, 0x83, 0xb6, 0x5f, 0x81, 0x7b, 0x5f, 0xce, 0xab, 0x01, + 0x81, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, 0x48, + 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x02, 0x01, 0x76, 0x66, + 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, + 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x81, 0xa1, 0x01, + 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + } + comid := Comid{} - err := comid.FromCBOR(testComidDesignCD) + err := comid.FromCBOR(in) if err != nil { fmt.Printf("FAIL: %v", err) } else { @@ -437,8 +534,41 @@ func Example_decode_CBOR_3() { } func Example_decode_CBOR_4() { + // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-firmware-cd.diag + in := []byte{ + 0xa3, 0x01, 0xa1, 0x00, 0x50, 0xaf, 0x1c, 0xd8, 0x95, 0xbe, 0x78, 0x4a, + 0xdb, 0xb7, 0xe9, 0xad, 0xd4, 0x4a, 0x65, 0xab, 0xf3, 0x02, 0x81, 0xa3, + 0x00, 0x71, 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x20, 0x4d, + 0x46, 0x47, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, 0xd8, 0x20, 0x78, 0x18, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x77, 0x6d, 0x66, + 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x02, 0x81, 0x00, 0x04, 0xa2, 0x00, 0x82, 0x82, 0xa1, 0x00, 0xa4, 0x01, + 0x70, 0x66, 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x67, 0x66, 0x77, 0x59, 0x5f, 0x6e, + 0x35, 0x78, 0x03, 0x00, 0x04, 0x00, 0x81, 0xa1, 0x01, 0xa2, 0x01, 0xd9, + 0x02, 0x28, 0x01, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x15, 0xe7, 0x7d, + 0x6f, 0x13, 0x32, 0x52, 0xf1, 0xdb, 0x70, 0x44, 0x90, 0x13, 0x13, 0x88, + 0x4f, 0x29, 0x77, 0xd2, 0x10, 0x9b, 0x33, 0xc7, 0x9f, 0x33, 0xe0, 0x79, + 0xbf, 0xc7, 0x88, 0x65, 0x25, 0x5c, 0x0f, 0xb7, 0x33, 0xc2, 0x40, 0xfd, + 0xda, 0x54, 0x4b, 0x82, 0x15, 0xd7, 0xb8, 0xf8, 0x15, 0x82, 0xa1, 0x00, + 0xa4, 0x01, 0x70, 0x66, 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x67, 0x66, 0x77, 0x58, + 0x5f, 0x6e, 0x35, 0x78, 0x03, 0x01, 0x04, 0x00, 0x81, 0xa1, 0x01, 0xa2, + 0x01, 0xd9, 0x02, 0x28, 0x01, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x3d, + 0x90, 0xb6, 0xbf, 0x00, 0x3d, 0xa2, 0xd9, 0x4e, 0xa5, 0x46, 0x3f, 0x97, + 0xfb, 0x3c, 0x53, 0xdd, 0xc5, 0x1c, 0xfb, 0xa1, 0xe3, 0xe3, 0x8e, 0xef, + 0x7a, 0xf0, 0x71, 0xa6, 0x79, 0x86, 0x59, 0x5d, 0x22, 0x72, 0x91, 0x31, + 0xdf, 0x9f, 0xe8, 0x0f, 0x54, 0x51, 0xee, 0xf1, 0x54, 0xf8, 0x5e, 0x01, + 0x81, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, 0x48, + 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x01, 0x01, 0x70, 0x66, + 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x81, 0xa1, 0x01, 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + } + comid := Comid{} - err := comid.FromCBOR(testComidFirmwareCD) + err := comid.FromCBOR(in) if err != nil { fmt.Printf("FAIL: %v", err) } else { @@ -449,8 +579,24 @@ func Example_decode_CBOR_4() { } func Example_decode_CBOR_5() { + // Taken from https://github.com/ietf-corim-cddl/blob/main/examples/comid-3.diag + in := []byte{ + 0xa3, 0x01, 0xa1, 0x00, 0x78, 0x20, 0x6d, 0x79, 0x2d, 0x6e, 0x73, 0x3a, + 0x61, 0x63, 0x6d, 0x65, 0x2d, 0x72, 0x6f, 0x61, 0x64, 0x72, 0x75, 0x6e, + 0x6e, 0x65, 0x72, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x02, 0x81, 0xa3, 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x01, 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, + 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x83, 0x01, 0x00, 0x02, 0x04, 0xa1, 0x00, + 0x81, 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x44, 0x55, 0x02, 0xc0, + 0x00, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x02, 0x78, 0x18, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x52, 0x6f, 0x61, 0x64, + 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x20, 0x46, 0x69, 0x72, 0x6d, 0x77, + 0x61, 0x72, 0x65, 0x81, 0xa2, 0x00, 0x19, 0x02, 0xbc, 0x01, 0xa1, 0x02, + 0x81, 0x82, 0x06, 0x44, 0xab, 0xcd, 0xef, 0x00, + } comid := Comid{} - err := comid.FromCBOR(testComid3) + err := comid.FromCBOR(in) if err != nil { fmt.Printf("FAIL: %v", err) } else { diff --git a/comid/measurement.go b/comid/measurement.go index 15d511e5..ac626246 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -773,3 +773,48 @@ func (o Measurement) Valid() error { return o.Val.Valid() } + +// Measurements is a container for Measurement instances and their extensions. +// It is a thin wrapper around extensions.Collection. +type Measurements extensions.Collection[Measurement, *Measurement] + +func NewMeasurements() *Measurements { + return (*Measurements)(extensions.NewCollection[Measurement]()) +} + +func (o *Measurements) RegisterExtensions(exts extensions.Map) error { + return (*extensions.Collection[Measurement, *Measurement])(o).RegisterExtensions(exts) +} + +func (o *Measurements) GetExtensions() extensions.IMapValue { + return (*extensions.Collection[Measurement, *Measurement])(o).GetExtensions() +} + +func (o *Measurements) Valid() error { + return (*extensions.Collection[Measurement, *Measurement])(o).Valid() +} + +func (o *Measurements) IsEmpty() bool { + return (*extensions.Collection[Measurement, *Measurement])(o).IsEmpty() +} + +func (o *Measurements) Add(val *Measurement) *Measurements { + ret := (*extensions.Collection[Measurement, *Measurement])(o).Add(val) + return (*Measurements)(ret) +} + +func (o Measurements) MarshalCBOR() ([]byte, error) { + return (extensions.Collection[Measurement, *Measurement])(o).MarshalCBOR() +} + +func (o *Measurements) UnmarshalCBOR(data []byte) error { + return (*extensions.Collection[Measurement, *Measurement])(o).UnmarshalCBOR(data) +} + +func (o Measurements) MarshalJSON() ([]byte, error) { + return (extensions.Collection[Measurement, *Measurement])(o).MarshalJSON() +} + +func (o *Measurements) UnmarshalJSON(data []byte) error { + return (*extensions.Collection[Measurement, *Measurement])(o).UnmarshalJSON(data) +} diff --git a/comid/test_vars.go b/comid/test_vars.go index 8b993e6d..ce3b59fa 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -234,75 +234,53 @@ var ( "model": "RoadRunner" } }, - "measurement": { - "key": { - "type": "psa.refval-id", + "measurements": [ + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "BL", + "version": "2.1.0", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } + }, "value": { - "label": "BL", - "version": "2.1.0", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + "digests": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + ] } }, - "value": { - "digests": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - ] - } - } - }, - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "PRoT", + "version": "1.3.5", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } }, - "vendor": "ACME", - "model": "RoadRunner" - } - }, - "measurement": { - "key": { - "type": "psa.refval-id", "value": { - "label": "PRoT", - "version": "1.3.5", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + "digests": [ + "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" + ] } }, - "value": { - "digests": [ - "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" - ] - } - } - }, - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "ARoT", + "version": "0.1.4", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } }, - "vendor": "ACME", - "model": "RoadRunner" - } - }, - "measurement": { - "key": { - "type": "psa.refval-id", "value": { - "label": "ARoT", - "version": "0.1.4", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + "digests": [ + "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" + ] } - }, - "value": { - "digests": [ - "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" - ] } - } + ] } ] } @@ -371,7 +349,7 @@ var ( } } ` - CCARefValJSONTemplate = ` { + CCARefValJSONTemplate = `{ "lang": "en-GB", "tag-identity": { "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", @@ -397,99 +375,65 @@ var ( "model": "RoadRunner" } }, - "measurement": { - "key": { - "type": "psa.refval-id", + "measurements": [ + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "BL", + "version": "2.1.0", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } + }, "value": { - "label": "BL", - "version": "2.1.0", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + "digests": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + ] } }, - "value": { - "digests": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - ] - } - } - }, - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "PRoT", + "version": "1.3.5", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } }, - "vendor": "ACME", - "model": "RoadRunner" - } - }, - "measurement": { - "key": { - "type": "psa.refval-id", "value": { - "label": "PRoT", - "version": "1.3.5", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + "digests": [ + "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" + ] } }, - "value": { - "digests": [ - "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" - ] - } - } - }, - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "ARoT", + "version": "0.1.4", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } }, - "vendor": "ACME", - "model": "RoadRunner" - } - }, - "measurement": { - "key": { - "type": "psa.refval-id", "value": { - "label": "ARoT", - "version": "0.1.4", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + "digests": [ + "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" + ] } }, - "value": { - "digests": [ - "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" - ] - } - } - }, - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + { + "key": { + "type": "cca.platform-config-id", + "value": "a non-empty (unique) label" }, - "vendor": "ACME", - "model": "RoadRunner" - } - }, - "measurement": { - "key": { - "type": "cca.platform-config-id", - "value": "a non-empty (unique) label" - }, - "value": { - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + "value": { + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } } } - } + ] } ] } @@ -500,35 +444,36 @@ var ( "tag-identity": { "id": "99019224-57AA-44BC-BEF8-D36BDD6BD035", "version": 0 - }, - "entities": [ +}, +"entities": [ + { + "name": "Workload Client Ltd.", + "regid": "https://workloadclient.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } +], +"triples": { + "reference-values": [ { - "name": "Workload Client Ltd.", - "regid": "https://workloadclient.example", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "uuid", - "value": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C" - }, - "vendor": "Workload Client Ltd" + "environment": { + "class": { + "id": { + "type": "uuid", + "value": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C" }, - "instance": { - "type": "bytes", - "value": "QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" - } + "vendor": "Workload Client Ltd" }, - "measurement": { + "instance": { + "type": "bytes", + "value": "QoS1aUymwNLPR4mguVrIAlyBjeUjBDZL580pgbLS7caFsyInfsJYGZYkE9jJssH1" + } + }, + "measurements": [ + { "value": { "raw-value": { "type": "bytes", @@ -568,9 +513,9 @@ var ( } } } - } - ] - } + ] + } + ] } -` +}` ) diff --git a/comid/testcases/comid-1.cbor b/comid/testcases/comid-1.cbor deleted file mode 100644 index 033b8ddd0ab3c3e1f7631e9b23ad845aab029e56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@Cx`E@D{3kZP!Bpl84+>A=9$*u)s2;Ihg%>&t186_w6ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@9G)9NJw=`vOgW(2~g;P6KCmst<-9V{L h2tN_BiLsHG;46ZLUVe!}ej3D6%pl(|-el5X1OT>wqEP?< diff --git a/comid/testcases/comid-3.cbor b/comid/testcases/comid-3.cbor deleted file mode 100644 index f47d1d1a6138ce4c7572037280425c7c66f8ded7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmZ3?xR9YjA-7UDuh=RvIX6|eC_gc!s5CDxwMe(Pw4fj-H8(Y{gsE{cL#Cs%ud9M* zUa}tJ4TX}7l9GaAD}8;C9=+6x#9W|Cre;P4CYFT^jZF&~7Bk$)cL`-Wz`%&m$5bH! mHX{gVMG)8u1-Hzi-15Yt)I|)EOnVp?GBq}_xvW0>o&f-IFf(!h diff --git a/comid/testcases/comid-design-cd.cbor b/comid/testcases/comid-design-cd.cbor deleted file mode 100644 index d736d6c5a7b1c70db5324001f6d149997af71a56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 607 zcmZ3&xR4=0Zq3zcUl#jso4J48);=yjcc#Y0425n1?v4sBsl}P;dBwUxx}n94Hxw%5 zGD=DcimmkZ(+biPQy`K>rNw%w6^Xe8IjKyI49ty-7y_n$UCu6(@7&*B<;BF_@cJbq z1Ir?YmZpUaiy3a@dndGcFt+{hW#ngJWGusO5Hr(4#zibQnG8G_pn%omKM;U8n8q;S zGiD)EV-tIXf&Ih2Qx3uH^BC*CzOJ$h3{RaCKYL?zE$7tP-EHnoT0VzltdG?U4v`I7bm6k+^+-tn#@wItdd}DR|xz&t~SX_oPm~qDy057lAq5uE@ diff --git a/comid/testcases/comid-firmware-cd.cbor b/comid/testcases/comid-firmware-cd.cbor deleted file mode 100644 index cb876a6e521693fb0928e2f13f69a06a746233f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 339 zcmZ3?xR4=Wz08fN`zpL{Z-2S=idX9D&rFSr84BGpi*m~oi&7PQ-P{#C^OE%#Zzxnq zWR#Q?6kF-*rV z@b^}Tca!M#9X~{ITMz-ZfDzpS+X>tDGuSS=>9^F)e)?~l;JZg /dev/null && pwd ) - -if [[ "$(type -p diag2cbor.rb)" == "" ]]; then - echo "ERROR: please install ruby-cbor-diag package" - exit 1 -fi - -for case in "$THIS_DIR"/src/*.diag; do - outfile=$(basename "${case%%.diag}").cbor - - echo "generating $outfile" - - diag2cbor.rb "$case" > "$THIS_DIR/$outfile" -done - -echo "done." diff --git a/comid/testcases/src/comid-1.diag b/comid/testcases/src/comid-1.diag deleted file mode 100644 index c756da18..00000000 --- a/comid/testcases/src/comid-1.diag +++ /dev/null @@ -1,37 +0,0 @@ -/ concise-mid-tag / { - / comid.tag-identity / 1 : { - / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' - }, - / comid.entity / 2 : [ { - / comid.entity-name / 0 : "ACME Inc.", - / comid.reg-id / 1 : 32("https://acme.example"), - / comid.role / 2 : [ 0 ] / tag-creator / - } ], - / comid.triples / 4 : { - / comid.reference-triples / 0 : [ [ - / environment-map / { - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-uuid-type / 37( - h'67b28b6c34cc40a19117ab5b05911e37' - ), - / comid.vendor / 1 : "ACME Inc.", - / comid.model / 2 : "ACME RoadRunner", - / comid.layer / 3 : 1 - } - }, - / measurement-map / { - / comid.mval / 1 : { - / comid.ver / 0 : { - / comid.version / 0 : "1.0.0", - / comid.version-scheme / 1 : 16384 / semver / - }, - / comid.digests / 2 : [ [ - / hash-alg-id / 1, / sha256 / - / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' - ] ] - } - } - ] ] - } -} diff --git a/comid/testcases/src/comid-2.diag b/comid/testcases/src/comid-2.diag deleted file mode 100644 index ce3c2752..00000000 --- a/comid/testcases/src/comid-2.diag +++ /dev/null @@ -1,97 +0,0 @@ -/ concise-mid-tag / { - / comid.tag-identity / 1 : { - / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' - }, - / comid.entity / 2 : [ { - / comid.entity-name / 0 : "ACME Inc.", - / comid.reg-id / 1 : 32("https://acme.example"), - / comid.role / 2 : [ 0 ] / tag-creator / - } ], - / comid.triples / 4 : { - / comid.reference-triples / 0 : [ - [ - / environment-map / { - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-uuid-type / 37( - h'67b28b6c34cc40a19117ab5b05911e37' - ), - / comid.vendor / 1 : "ACME Inc.", - / comid.model / 2 : "ACME RoadRunner Firmware", - / comid.layer / 3 : 1 - } - }, - / measurement-map / { - / comid.mval / 1 : { - / comid.digests / 2 : [ [ - / hash-alg-id / 1, / sha256 / - / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' - ] ] - } - } - ], - [ - / environment-map / { - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-uuid-type / 37( - h'a71b3e388d454a0581f352e58c832c5c' - ), - / comid.vendor / 1 : "WYLIE Inc.", - / comid.model / 2 : "WYLIE Coyote Trusted OS", - / comid.layer / 3 : 2, - / comid.index / 4 : 0 - } - }, - / measurement-map / { - / comid.mval / 1 : { - / comid.digests / 2 : [ [ - / hash-alg-id / 1, / sha256 / - / hash-value / h'bb71198ed60a95dc3c619e555c2c0b8d7564a38031b034a195892591c65365b0' - ] ] - } - } - ], - [ - / environment-map / { - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-uuid-type / 37( - h'a71b3e388d454a0581f352e58c832c5c' - ), - / comid.vendor / 1 : "WYLIE Inc.", - / comid.model / 2 : "WYLIE Coyote Trusted OS", - / comid.layer / 3 : 2, - / comid.index / 4 : 1 - } - }, - / measurement-map / { - / comid.mval / 1 : { - / comid.digests / 2 : [ [ - / hash-alg-id / 1, / sha256 / - / hash-value / h'bb71198ed60a95dc3c619e555c2c0b8d7564a38031b034a195892591c65365b0' - ] ] - } - } - ] - ], - / comid.endorsed-triples / 1 : [ [ - / environment-map / { - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-uuid-type / 37( - h'67b28b6c34cc40a19117ab5b05911e37' - ), - / comid.vendor / 1 : "ACME Inc.", - / comid.model / 2 : "ACME Root of Trust", - / comid.layer / 3 : 0 - } - }, - / measurement-map / { - / comid.mval / 1 : { - / comid.svn / 1 : 552(1) - } - } - ] ] - } -} diff --git a/comid/testcases/src/comid-3.diag b/comid/testcases/src/comid-3.diag deleted file mode 100644 index c11fc237..00000000 --- a/comid/testcases/src/comid-3.diag +++ /dev/null @@ -1,34 +0,0 @@ -/ concise-mid-tag / { - / comid.tag-identity / 1 : { - / comid.tag-id / 0 : "my-ns:acme-roadrunner-supplement" - }, - / comid.entity / 2 : [ { - / comid.entity-name / 0 : "ACME Inc.", - / comid.reg-id / 1 : 32("https://acme.example"), - / comid.role / 2 : [ 1,0,2 ] / creator, tag-creator, maintainer / - } ], - / comid.triples / 4 : { - / comid.reference-triples / 0 : [ - [ - / environment-map / { - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-oid-type / 111( - h'5502C000' - ), - / comid.vendor / 1 : "ACME Inc.", - / comid.model / 2 : "ACME RoadRunner Firmware" - } - }, - / measurement-map / { - / comid.mkey / 0: 700, - / comid.mval / 1 : { - / comid.digests / 2 : [[ - / hash-alg-id / 6, / sha-256-32 / - / hash-value / h'ABCDEF00' ]] - } - } - ] - ] - } -} diff --git a/comid/testcases/src/comid-design-cd.diag b/comid/testcases/src/comid-design-cd.diag deleted file mode 100644 index d3305b56..00000000 --- a/comid/testcases/src/comid-design-cd.diag +++ /dev/null @@ -1,119 +0,0 @@ -/ concise-mid-tag / { - / comid.tag-identity / 1 : { - / comid.tag-id / 0 : h'1EACD596F4A34FB699BFAEB58E0A4E47' - }, - / comid.entity / 2 : [ { - / comid.entity-name / 0 : "FPGA Designs-R-Us", - / comid.reg-id / 1 : 32("https://fpgadesignsrus.example"), - / comid.role / 2 : [ 0 ] / tag-creator / - } ], - / comid.linked-tags / 3 : [ { - / comid.linked-tag-id / 0 : h'97F5A7071C6F438F877A4A020780EBE9', - / comid.tag-rel / 1 : / comid.supplements / 0 - } - ], - / comid.triples / 4 : { - / comid.reference-triples / 0 : [ - [ - / environment-map / { - / ** Layer 3 device state ** / - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-oid-type / 111(h'6086480186F84D010F0401'), / 2.16.840.1.113741.1.15.4.1 / - / comid.vendor / 1 : "fpgadesignsrus.example", - / comid.layer / 3 : 2 - } - }, - / measurement-map / - { - / comid.mval / 1 : { - / raw-value-group / - / comid.raw-value / 4 : 560(h'0000000000000000'), - / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' - } - } - ], - [ - / environment-map / { - / ** Layer 2 design (IO descriptor) hash ** / - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-oid-type / 111(h'6086480186F84D010F0402'), / 2.16.840.1.113741.1.15.4.2 / - / comid.vendor / 1 : "fpgadesignsrus.example", - / comid.layer / 3 : 2 - } - }, - / measurement-map / - { - / comid.mval / 1 : { - / comid.digests / 2 : [ - [ - / hash-alg-id / 7, / SHA384 / - / hash-value / h'3FE18ECA4053879E017EF5EB7A3E5157659C5F9BB15B7D09959B8B8647822A4CC21C3AA6721CEF87F5BFA53495DB0833' - ] - ] - } - } - ], - [ - / environment-map / { - / ** Layer 2 design (CORE descriptor) hash ** / - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-oid-type / 111(h'6086480186F84D010F0403'), / 2.16.840.1.113741.1.15.4.3 / - / comid.vendor / 1 : "fpgadesignsrus.example", - / comid.layer / 3 : 2 - } - }, - / measurement-map / - { - / comid.mval / 1 : { - / comid.digests / 2 : [ - [ - / hash-alg-id / 7, / SHA384 / - / hash-value / h'20FF681A0882E29B481953888936209CB53DF9C5AAEC606A2C24A0FB138595124B8E3F24A12771BC3854CC68B40361AD' - ] - ] - } - } - ], - [ - / environment-map / { - / ** Firmware is valid (example assertion) ** / - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / - / comid.vendor / 1 : "fpgadesignsrus.example" - } - }, - / measurement-map / - { - / comid.mval / 1 : { - / raw-value-group / - / comid.raw-value / 4 : 560(h'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), - / comid.raw-value-mask / 5 : h'466224343D681802C1506BBED7D7F00B969BADDD6346E4F2E7CE146692996F22A45814DE81D248F583B65F817B5FCEAB' - } - } - ] - ], - / comid.endorsed-triples / 1 : [ - [ - / environment-map / { - / ** Design is valid (example assertion) ** / - / comid.class / 0 : { - / comid.class-id / 0 : - / tagged-oid-type / 111(h'6086480186F84D010F046302'), / 2.16.840.1.113741.1.15.4.99.2 / - / comid.vendor / 1 : "fpgadesignsrus.example" - } - }, - / measurement-map / { - / comid.mval / 1 : { - / raw-value-group / - / comid.raw-value / 4 : 560(h'0000000000000000'), - / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' - } - } - ] - ] - } - } diff --git a/comid/testcases/src/comid-firmware-cd.diag b/comid/testcases/src/comid-firmware-cd.diag deleted file mode 100644 index 1e8625fc..00000000 --- a/comid/testcases/src/comid-firmware-cd.diag +++ /dev/null @@ -1,80 +0,0 @@ - - / concise-mid-tag / { - / comid.tag-identity / 1 : { - / comid.tag-id / 0 : h'AF1CD895BE784ADBB7E9ADD44A65ABF3' - }, - / comid.entity / 2 : [ { - / comid.entity-name / 0 : "Firmware MFG Inc.", - / comid.reg-id / 1 : 32("https://fwmfginc.example"), - / comid.role / 2 : [ 0 ] / tag-creator / - } ], - / comid.triples / 4 : { - / comid.reference-triples / 0 : [ - [ - / environment-map / { - / ** Hash of layer 0 firmware ** / - / comid.class / 0 : { - / comid.vendor / 1 : "fwmfginc.example", - / comid.model / 2 : "fwY_n5x", - / comid.layer / 3 : 0, - / comid.index / 4 : 0 - } - }, - / measurement-map / - { - / comid.mval / 1 : { - / comid.svn / 1 : 552(1), - / comid.digests / 2 : [ - [ - / hash-alg-id / 7, / SHA384 / - / hash-value / h'15E77D6F133252F1DB7044901313884F2977D2109B33C79F33E079BFC78865255C0FB733C240FDDA544B8215D7B8F815' - ] - ] - } - } - ], - [ - / environment-map / { - / ** Hash of layer 1 firmware ** / - / comid.class / 0 : { - / comid.vendor / 1 : "fwmfginc.example", - / comid.model / 2 : "fwX_n5x", - / comid.layer / 3 : 1, - / comid.index / 4 : 0 - } - }, - / measurement-map / - { - / comid.mval / 1 : { - / comid.svn / 1 : 552(1), - / comid.digests / 2 : [ - [ - / hash-alg-id / 7, / SHA384 / - / hash-value / h'3D90B6BF003DA2D94EA5463F97FB3C53DDC51CFBA1E3E38EEF7AF071A67986595D22729131DF9FE80F5451EEF154F85E' - ] - ] - } - } - ] - ], - / comid.endorsed-triples / 1 : [ - [ - / environment-map / { - / comid.class / 0 : { - / ** Firmware is valid (example) ** / - / comid.class-id / 0 : - / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / - / comid.vendor / 1 : "fwmfginc.example" - } - }, - / measurement-map / { - / comid.mval / 1 : { - / raw-value-group / - / comid.raw-value / 4 : 560(h'0000000000000000'), - / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' - } - } - ] - ] - } - } diff --git a/comid/valuetriple.go b/comid/valuetriple.go index 9e175ce8..a7662ea0 100644 --- a/comid/valuetriple.go +++ b/comid/valuetriple.go @@ -4,28 +4,29 @@ package comid import ( + "errors" "fmt" "github.com/veraison/corim/extensions" ) -// ValueTriple relates a measurement to a target environment, essentially -// forming a subject-predicate-object triple of -// "measurement-pertains-to-environment". This structure is used to represent -// both reference-triple-record and endorsed-triple-record in the CoRIM spec -// (as of rev. 04). +// ValueTriple relates measurements to a target environment, essentially +// forming a subject-predicate-object triple of "measurements-pertain +// to-environment". This structure is used to represent both +// reference-triple-record and endorsed-triple-record in the CoRIM spec (as of +// rev. 04). type ValueTriple struct { - _ struct{} `cbor:",toarray"` - Environment Environment `json:"environment"` - Measurement Measurement `json:"measurement"` + _ struct{} `cbor:",toarray"` + Environment Environment `json:"environment"` + Measurements Measurements `json:"measurements"` } func (o *ValueTriple) RegisterExtensions(exts extensions.Map) error { - return o.Measurement.RegisterExtensions(exts) + return o.Measurements.RegisterExtensions(exts) } func (o *ValueTriple) GetExtensions() extensions.IMapValue { - return o.Measurement.GetExtensions() + return o.Measurements.GetExtensions() } func (o ValueTriple) Valid() error { @@ -33,8 +34,12 @@ func (o ValueTriple) Valid() error { return fmt.Errorf("environment validation failed: %w", err) } - if err := o.Measurement.Valid(); err != nil { - return fmt.Errorf("measurement validation failed: %w", err) + if o.Measurements.IsEmpty() { + return errors.New("measurements validation failed: no measurement entries") + } + + if err := o.Measurements.Valid(); err != nil { + return fmt.Errorf("measurements validation failed: %w", err) } return nil diff --git a/comid/valuetriple_test.go b/comid/valuetriple_test.go index 857bcf2b..0888e32c 100644 --- a/comid/valuetriple_test.go +++ b/comid/valuetriple_test.go @@ -19,5 +19,5 @@ func Test_ReferenceValue(t *testing.T) { require.NoError(t, err) rv.Environment.Instance = MustNewUUIDInstance(id) err = rv.Valid() - assert.EqualError(t, err, "measurement validation failed: no measurement value set") + assert.EqualError(t, err, "measurements validation failed: no measurement entries") } diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index b62a9da5..becff460 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -104,10 +104,10 @@ func Example_profile_unmarshal() { Extensions.MustGetString("Address")) fmt.Printf("Measurements:\n") - for _, refVal := range extractedComid.Triples.ReferenceValues.Values { + for _, m := range extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values { - val := hex.EncodeToString((*refVal.Measurement.Val.Digests)[0].HashValue) - tsInt := refVal.Measurement.Val.Extensions.MustGetInt64("timestamp") + val := hex.EncodeToString((*m.Val.Digests)[0].HashValue) + tsInt := m.Val.Extensions.MustGetInt64("timestamp") ts := time.Unix(tsInt, 0).UTC() fmt.Printf(" %v taken at %s\n", val, ts.Format("2006-01-02T15:04:05")) @@ -152,6 +152,15 @@ func Example_profile_marshal() { log.Fatalf("could not set entity Address: %v", err) } + refVal := comid.ValueTriple{ + Environment: comid.Environment{ + Class: comid.NewClassImplID(comid.TestImplID). + SetVendor("ACME Ltd."). + SetModel("RoadRunner 2.0"), + }, + Measurements: *comid.NewMeasurements(), + } + measurement := comid.MustNewPSAMeasurement( comid.MustCreatePSARefValID( comid.TestSignerID, "BL", "5.0.5", @@ -168,15 +177,7 @@ func Example_profile_marshal() { log.Fatal("could not register refval extensions") } - refVal := comid.ValueTriple{ - Environment: comid.Environment{ - Class: comid.NewClassImplID(comid.TestImplID). - SetVendor("ACME Ltd."). - SetModel("RoadRunner 2.0"), - }, - Measurement: *measurement, - } - + refVal.Measurements.Add(measurement) myComid.Triples.AddReferenceValue(refVal) err = myComid.Valid() @@ -194,5 +195,5 @@ func Example_profile_marshal() { fmt.Printf("corim: %v", hex.EncodeToString(buf)) // output: - // corim: a300f6018158d8d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e30a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 + // corim: a300f6018158d9d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 } diff --git a/corim/profiles_test.go b/corim/profiles_test.go index 34c5e89e..4b4a917e 100644 --- a/corim/profiles_test.go +++ b/corim/profiles_test.go @@ -126,7 +126,7 @@ func TestProfile_marshaling(t *testing.T) { address := cmd.Entities.Values[0].Extensions.MustGetString("Address") assert.Equal(t, "123 Fake Street", address) - ts := cmd.Triples.ReferenceValues.Values[0].Measurement. + ts := cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. Val.Extensions.MustGetInt("timestamp") assert.Equal(t, 1720782190, ts) @@ -169,7 +169,7 @@ func TestProfile_marshaling(t *testing.T) { address = cmd.Entities.Values[0].Extensions.MustGetString("Address") assert.Equal(t, "123 Fake Street", address) - ts = cmd.Triples.ReferenceValues.Values[0].Measurement. + ts = cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. Val.Extensions.MustGetInt("timestamp") assert.Equal(t, 1720782190, ts) diff --git a/corim/testcases/comid-ext.json b/corim/testcases/comid-ext.json index a670c1ca..541113ec 100644 --- a/corim/testcases/comid-ext.json +++ b/corim/testcases/comid-ext.json @@ -29,19 +29,21 @@ "model": "RoadRunner" } }, - "measurement": { - "key": { - "type": "cca.platform-config-id", - "value": "cfg v1.0.0" - }, - "value": { - "timestamp": 1720782190, - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" - } - } - } + "measurements": [ + { + "key": { + "type": "cca.platform-config-id", + "value": "cfg v1.0.0" + }, + "value": { + "timestamp": 1720782190, + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } + } + } + ] } ] } diff --git a/corim/testcases/comid.json b/corim/testcases/comid.json index 9c4125bc..974a473b 100644 --- a/corim/testcases/comid.json +++ b/corim/testcases/comid.json @@ -28,18 +28,20 @@ "model": "RoadRunner" } }, - "measurement": { - "key": { - "type": "cca.platform-config-id", - "value": "cfg v1.0.0" - }, - "value": { - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" - } - } - } + "measurements": [ + { + "key": { + "type": "cca.platform-config-id", + "value": "cfg v1.0.0" + }, + "value": { + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } + } + } + ] } ] } diff --git a/corim/testcases/signed-corim-with-extensions.cbor b/corim/testcases/signed-corim-with-extensions.cbor index eb03f4ad7520bb67d733aa12b390beacf7840314..4bfca682959be82d3ace80585e5e20241a5f7759 100644 GIT binary patch delta 236 zcmVpxfusT0AarPSbZ~PzFE3$ZZDlTHcwudDY-N#| zAqWJa0D*#`0HcxYCX+q_5)y-=0NDarqXA+A~BWLQ8DEQn~y6W)ZU!NT-eKje9#78O`Hv*Y4*b=BwKo0G}*CgZOqOpQw>o>4Sn zyrEE%QBqP+Y^ATCn4Fucms*jSTac5=vXG&++>PeJeiS6g)7O)hb7fW&rr``auTDS07wt2Y8}Q!llL>4PM*l9H}Rv*ymW=^)Jn!13MCmOB?ZM+`ud5cOp zEn#Y0GI59O#5po7EDISLC*HJSZ(hW3lPPlXLp_7ZVvKsi5E;gl zfS~*kmQ+JMV?9%lvdLQ+b$MWNjzGD|kxbf?yO=zjA{=hFHcHob?RyU_~bLD)iLq6GU%X?b7X~S*{#Z#YlS;+7#F986; CBVkOCfl2gzm_mHE}3{jX5wmD7M6t! z%@bePFfN)L$*3p5aFZ!=F=LXG4@;_%o}r$>SHfYJ&Iqt<9X`hwY&G{7`wO|{`mXO#%V5}$~$K!1%@J(<@%Dj>W|h* buR9Q$dauOr(ws#CnTjtL31;0{Xd(mvu3T%+ diff --git a/corim/testcases/signed-good-corim.cbor b/corim/testcases/signed-good-corim.cbor index f08557b59fef989d5d3dc1d5e30dff506d6e6b5e..50dae69118fff2b0e11127df304879307902dcb1 100644 GIT binary patch delta 216 zcmcb{dY@&2u^2;%qqDE8f=@|`LUCq#US?jpLUwB9#5oeG3nCeJEMmwlNi8l>NX{?H z%vH!tVQh?KTzr%9*AkY642?|-85U12VAPqc#l*wjyoli@Q{>{w>lsxh>oFQlp2HX? z3=v{XaSX~2VM#U6Gt@Hy%4<)yXHuM4FDt;*%)rRRkO{M24```Vgu{B4m)2*C+!gA= z>>dSaM^t*GOT~7nNnc!P_{=-rGPX;2A%CJpFE2})_P&jC|Hkb6#k@Vc??&wJYdaYo J(<}QQ004VXPDB6z delta 280 zcmcc5a*cI@u{Yxlg_4Ytl7eC@ef`Ab+*G~Pip1Q4oK%JqM`vGG1)q`>h2qTgyv)3G zh3wSI1(8hpix_fCQj1FzlJko)a}_dE7#ky*q;4|)S~B@Mqnr~i^O%|$7?~I{VTS6l zEM#bITF9`NF$E;Vlogbpm=aW)mzP>Ju}y8_{ulv}B&x8^WNF5@iA^?)izcsU)a1U& z6uFos)j-cs&t$S5ql$ZqV^Drbgu}FTkF%W*NR}H){Vlkp8n7YFVQ;H>ard3b2gf`* pe*bk5na5yk)zQWN%ytLUBbL8rOExWDbwRO6N%bCo@a1D`%mJW(X)XW& diff --git a/corim/testcases/src/corim-with-extensions.yaml b/corim/testcases/src/corim-with-extensions.yaml index 0eec372e..2f1a45f1 100644 --- a/corim/testcases/src/corim-with-extensions.yaml +++ b/corim/testcases/src/corim-with-extensions.yaml @@ -33,57 +33,43 @@ YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= 1: ACME 2: RoadRunner - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - -1: 1720782190 - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - -1: 1720782190 - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= - -1: 1720782190 + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 diff --git a/corim/testcases/src/example-corim.yaml b/corim/testcases/src/example-corim.yaml index 7dff7941..55d45847 100644 --- a/corim/testcases/src/example-corim.yaml +++ b/corim/testcases/src/example-corim.yaml @@ -36,57 +36,43 @@ YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= 1: ACME 2: RoadRunner - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - -1: 1720782190 - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - -1: 1720782190 - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= - -1: 1720782190 + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 diff --git a/corim/testcases/src/good-corim.yaml b/corim/testcases/src/good-corim.yaml index ca12ebe8..4e61fee6 100644 --- a/corim/testcases/src/good-corim.yaml +++ b/corim/testcases/src/good-corim.yaml @@ -27,54 +27,40 @@ YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= 1: ACME 2: RoadRunner - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/corim/testcases/unsigned-corim-with-extensions.cbor b/corim/testcases/unsigned-corim-with-extensions.cbor index a67d06388ac3d70013dce48a964dbbeb691974f6..0e056df92a7d13cf5caff8df0858e92e82bb1894 100644 GIT binary patch delta 124 zcmey#(!?@Bo0sv}O~zkK7*bR7blsgM8j4Sh)DTt3H#9OkT%wSiUzC}vkeLDiK6)tA delta 188 zcmZo-`N=XtTaU^1CgZOqj0+h8oOeI2*Gu#^UUxb8{70v5F{Z{P3i*ac#tLqU*{KS_ zB}J*JB@_KMSXdS^G*9fYVO%uvhw;QydMr%~85U2Rqa(_c6_lTt5>%R(ms+%lagl;l z+C<*G$;pgrlXVzPVXEX&R59FSid@W?;uw@4!jfvBXQ*c~*^W__4XX7xqna2)YHFUY VyAwlhNosM4LUMjlX0AeJ3IJyqK~4Yw diff --git a/corim/testcases/unsigned-example-corim.cbor b/corim/testcases/unsigned-example-corim.cbor index fff8f2e0c1583e05a9b6e623eb85a1cb218b2c7d..0c715c8f24d0886906a5f8df3eb40b02d6b4d4dd 100644 GIT binary patch delta 158 zcmeyv(#pcOgt0M_@z_nqUrSgPGBi${Zo}8Sh~XwvTxqQQbWI8(g zx+?gTr07ka%&5o0vXG&9BA?A Date: Thu, 3 Oct 2024 11:23:14 +0100 Subject: [PATCH 053/110] fix(comid): Introduce CBOR Diag examples and new CoMID test cases Signed-off-by: Yogesh Deshpande --- comid/example_cca_realm_refval_test.go | 3 - comid/example_cca_refval_test.go | 3 - comid/example_psa_refval_test.go | 5 +- comid/example_test.go | 275 +++++---------------- comid/measurement.go | 1 - comid/test_vars.go | 2 +- comid/testcases/comid-1.cbor | Bin 0 -> 175 bytes comid/testcases/comid-2.cbor | Bin 0 -> 454 bytes comid/testcases/comid-3.cbor | Bin 0 -> 152 bytes comid/testcases/comid-4.cbor | Bin 0 -> 229 bytes comid/testcases/comid-5.cbor | Bin 0 -> 233 bytes comid/testcases/comid-design-cd.cbor | Bin 0 -> 612 bytes comid/testcases/comid-firmware-cd.cbor | Bin 0 -> 342 bytes comid/testcases/regen-from-src.sh | 21 ++ comid/testcases/src/comid-1.diag | 39 +++ comid/testcases/src/comid-2.diag | 105 ++++++++ comid/testcases/src/comid-3.diag | 36 +++ comid/testcases/src/comid-4.diag | 51 ++++ comid/testcases/src/comid-5.diag | 53 ++++ comid/testcases/src/comid-design-cd.diag | 129 ++++++++++ comid/testcases/src/comid-firmware-cd.diag | 86 +++++++ 21 files changed, 584 insertions(+), 225 deletions(-) create mode 100644 comid/testcases/comid-1.cbor create mode 100644 comid/testcases/comid-2.cbor create mode 100644 comid/testcases/comid-3.cbor create mode 100644 comid/testcases/comid-4.cbor create mode 100644 comid/testcases/comid-5.cbor create mode 100644 comid/testcases/comid-design-cd.cbor create mode 100644 comid/testcases/comid-firmware-cd.cbor create mode 100644 comid/testcases/regen-from-src.sh create mode 100644 comid/testcases/src/comid-1.diag create mode 100644 comid/testcases/src/comid-2.diag create mode 100644 comid/testcases/src/comid-3.diag create mode 100644 comid/testcases/src/comid-4.diag create mode 100644 comid/testcases/src/comid-5.diag create mode 100644 comid/testcases/src/comid-design-cd.diag create mode 100644 comid/testcases/src/comid-firmware-cd.diag diff --git a/comid/example_cca_realm_refval_test.go b/comid/example_cca_realm_refval_test.go index b2965eb7..a8907f3a 100644 --- a/comid/example_cca_realm_refval_test.go +++ b/comid/example_cca_realm_refval_test.go @@ -72,11 +72,9 @@ func extractRealmRefVal(rv ValueTriple) error { } measurements := rv.Measurements - if err := extractMeasurements(measurements); err != nil { return fmt.Errorf("extracting measurements: %w", err) } - return nil } @@ -84,7 +82,6 @@ func extractMeasurements(m Measurements) error { if len(m.Values) == 0 { return fmt.Errorf("no measurements") } - for i, meas := range m.Values { if err := extractMeasurement(meas); err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) diff --git a/comid/example_cca_refval_test.go b/comid/example_cca_refval_test.go index 92d0d49d..1717fa56 100644 --- a/comid/example_cca_refval_test.go +++ b/comid/example_cca_refval_test.go @@ -19,7 +19,6 @@ func Example_cca_refval() { if err := extractCcaRefVals(&comid); err != nil { panic(err) } - // output: // ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b @@ -66,7 +65,6 @@ func extractCCARefVal(rv ValueTriple) error { if !m.Key.IsSet() { return fmt.Errorf("mKey not set at index %d", i) } - switch t := m.Key.Value.(type) { case *TaggedPSARefValID: if err := extractSwMeasurement(m); err != nil { @@ -82,7 +80,6 @@ func extractCCARefVal(rv ValueTriple) error { default: return fmt.Errorf("unexpected Mkey type: %T", t) } - } return nil diff --git a/comid/example_psa_refval_test.go b/comid/example_psa_refval_test.go index 7fa78313..18308181 100644 --- a/comid/example_psa_refval_test.go +++ b/comid/example_psa_refval_test.go @@ -19,7 +19,6 @@ func Example_psa_refval() { if err := extractRefVals(&comid); err != nil { panic(err) } - // output: // ImplementationID: 61636d652d696d706c656d656e746174696f6e2d69642d303030303030303031 // SignerID: acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b @@ -34,6 +33,7 @@ func Example_psa_refval() { // Label: ARoT // Version: 0.1.4 // Digest: a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 + } func extractRefVals(c *Comid) error { @@ -58,7 +58,6 @@ func extractPSARefVal(rv ValueTriple) error { } measurements := rv.Measurements - if err := extractSwMeasurements(measurements); err != nil { return fmt.Errorf("extracting measurements: %w", err) } @@ -70,13 +69,11 @@ func extractSwMeasurements(m Measurements) error { if len(m.Values) == 0 { return fmt.Errorf("no measurements") } - for i, m := range m.Values { if err := extractSwMeasurement(m); err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } } - return nil } diff --git a/comid/example_test.go b/comid/example_test.go index d94b8fa8..e807be6e 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -4,7 +4,9 @@ package comid import ( + _ "embed" "fmt" + "testing" "github.com/google/uuid" "github.com/veraison/swid" @@ -381,227 +383,74 @@ func Example_decode_JSON() { // Output: OK } -func Example_decode_CBOR_1() { - // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-1.diag - in := []byte{ - 0xa3, 0x01, 0xa1, 0x00, 0x50, 0x3f, 0x06, 0xaf, 0x63, 0xa9, 0x3c, 0x11, - 0xe4, 0x97, 0x97, 0x00, 0x50, 0x56, 0x90, 0x77, 0x3f, 0x02, 0x81, 0xa3, - 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, - 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, - 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, - 0x81, 0x00, 0x04, 0xa1, 0x00, 0x81, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, - 0x25, 0x50, 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, - 0xab, 0x5b, 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x6f, 0x41, 0x43, 0x4d, 0x45, 0x20, - 0x52, 0x6f, 0x61, 0x64, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x03, 0x01, - 0x81, 0xa1, 0x01, 0xa2, 0x00, 0xa2, 0x00, 0x65, 0x31, 0x2e, 0x30, 0x2e, - 0x30, 0x01, 0x19, 0x40, 0x00, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x44, - 0xaa, 0x33, 0x6a, 0xf4, 0xcb, 0x14, 0xa8, 0x79, 0x43, 0x2e, 0x53, 0xdd, - 0x65, 0x71, 0xc7, 0xfa, 0x9b, 0xcc, 0xaf, 0xb7, 0x5f, 0x48, 0x82, 0x59, - 0x26, 0x2d, 0x6e, 0xa3, 0xa4, 0xd9, 0x1b, - } - - comid := Comid{} - err := comid.FromCBOR(in) - if err != nil { - fmt.Printf("FAIL: %v", err) - } else { - fmt.Println("OK") - } +var ( + // test cases are based on diag files here: + // https://github.com/ietf-rats-wg/draft-ietf-rats-corim/tree/main/cddl/examples - // Output: OK -} - -func Example_decode_CBOR_2() { - // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-2.diag - in := []byte{ - 0xa3, 0x01, 0xa1, 0x00, 0x50, 0x3f, 0x06, 0xaf, 0x63, 0xa9, 0x3c, 0x11, - 0xe4, 0x97, 0x97, 0x00, 0x50, 0x56, 0x90, 0x77, 0x3f, 0x02, 0x81, 0xa3, - 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, - 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, - 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, - 0x81, 0x00, 0x04, 0xa2, 0x00, 0x83, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, - 0x25, 0x50, 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, - 0xab, 0x5b, 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, - 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x78, 0x18, 0x41, 0x43, 0x4d, 0x45, - 0x20, 0x52, 0x6f, 0x61, 0x64, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x20, - 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x03, 0x01, 0x81, 0xa1, - 0x01, 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x44, 0xaa, 0x33, 0x6a, - 0xf4, 0xcb, 0x14, 0xa8, 0x79, 0x43, 0x2e, 0x53, 0xdd, 0x65, 0x71, 0xc7, - 0xfa, 0x9b, 0xcc, 0xaf, 0xb7, 0x5f, 0x48, 0x82, 0x59, 0x26, 0x2d, 0x6e, - 0xa3, 0xa4, 0xd9, 0x1b, 0x82, 0xa1, 0x00, 0xa5, 0x00, 0xd8, 0x25, 0x50, - 0xa7, 0x1b, 0x3e, 0x38, 0x8d, 0x45, 0x4a, 0x05, 0x81, 0xf3, 0x52, 0xe5, - 0x8c, 0x83, 0x2c, 0x5c, 0x01, 0x6a, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x77, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, - 0x43, 0x6f, 0x79, 0x6f, 0x74, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x65, 0x64, 0x20, 0x4f, 0x53, 0x03, 0x02, 0x04, 0x00, 0x81, 0xa1, 0x01, - 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0xbb, 0x71, 0x19, 0x8e, 0xd6, - 0x0a, 0x95, 0xdc, 0x3c, 0x61, 0x9e, 0x55, 0x5c, 0x2c, 0x0b, 0x8d, 0x75, - 0x64, 0xa3, 0x80, 0x31, 0xb0, 0x34, 0xa1, 0x95, 0x89, 0x25, 0x91, 0xc6, - 0x53, 0x65, 0xb0, 0x82, 0xa1, 0x00, 0xa5, 0x00, 0xd8, 0x25, 0x50, 0xa7, - 0x1b, 0x3e, 0x38, 0x8d, 0x45, 0x4a, 0x05, 0x81, 0xf3, 0x52, 0xe5, 0x8c, - 0x83, 0x2c, 0x5c, 0x01, 0x6a, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x02, 0x77, 0x57, 0x59, 0x4c, 0x49, 0x45, 0x20, 0x43, - 0x6f, 0x79, 0x6f, 0x74, 0x65, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, - 0x64, 0x20, 0x4f, 0x53, 0x03, 0x02, 0x04, 0x01, 0x81, 0xa1, 0x01, 0xa1, - 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0xbb, 0x71, 0x19, 0x8e, 0xd6, 0x0a, - 0x95, 0xdc, 0x3c, 0x61, 0x9e, 0x55, 0x5c, 0x2c, 0x0b, 0x8d, 0x75, 0x64, - 0xa3, 0x80, 0x31, 0xb0, 0x34, 0xa1, 0x95, 0x89, 0x25, 0x91, 0xc6, 0x53, - 0x65, 0xb0, 0x01, 0x81, 0x82, 0xa1, 0x00, 0xa4, 0x00, 0xd8, 0x25, 0x50, - 0x67, 0xb2, 0x8b, 0x6c, 0x34, 0xcc, 0x40, 0xa1, 0x91, 0x17, 0xab, 0x5b, - 0x05, 0x91, 0x1e, 0x37, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, - 0x6e, 0x63, 0x2e, 0x02, 0x72, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x52, 0x6f, - 0x6f, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x03, - 0x00, 0x81, 0xa1, 0x01, 0xa1, 0x01, 0xd9, 0x02, 0x28, 0x01, - } - - comid := Comid{} - err := comid.FromCBOR(in) - if err != nil { - fmt.Printf("FAIL: %v", err) - } else { - fmt.Println("OK") - } + //go:embed testcases/comid-1.cbor + testComid1 []byte - // Output: OK -} + //go:embed testcases/comid-2.cbor + testComid2 []byte -func Example_decode_CBOR_3() { - // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-design-cd.diag - in := []byte{ - 0xa4, 0x01, 0xa1, 0x00, 0x50, 0x1e, 0xac, 0xd5, 0x96, 0xf4, 0xa3, 0x4f, - 0xb6, 0x99, 0xbf, 0xae, 0xb5, 0x8e, 0x0a, 0x4e, 0x47, 0x02, 0x81, 0xa3, - 0x00, 0x71, 0x46, 0x50, 0x47, 0x41, 0x20, 0x44, 0x65, 0x73, 0x69, 0x67, - 0x6e, 0x73, 0x2d, 0x52, 0x2d, 0x55, 0x73, 0x01, 0xd8, 0x20, 0x78, 0x1e, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x70, 0x67, 0x61, - 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, 0x65, - 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x81, 0x00, 0x03, 0x81, 0xa2, - 0x00, 0x50, 0x97, 0xf5, 0xa7, 0x07, 0x1c, 0x6f, 0x43, 0x8f, 0x87, 0x7a, - 0x4a, 0x02, 0x07, 0x80, 0xeb, 0xe9, 0x01, 0x00, 0x04, 0xa2, 0x00, 0x84, - 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, - 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x01, 0x01, 0x76, 0x66, 0x70, 0x67, - 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, - 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, - 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x02, 0x01, 0x76, 0x66, 0x70, 0x67, - 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, - 0xa1, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x3f, 0xe1, 0x8e, 0xca, 0x40, - 0x53, 0x87, 0x9e, 0x01, 0x7e, 0xf5, 0xeb, 0x7a, 0x3e, 0x51, 0x57, 0x65, - 0x9c, 0x5f, 0x9b, 0xb1, 0x5b, 0x7d, 0x09, 0x95, 0x9b, 0x8b, 0x86, 0x47, - 0x82, 0x2a, 0x4c, 0xc2, 0x1c, 0x3a, 0xa6, 0x72, 0x1c, 0xef, 0x87, 0xf5, - 0xbf, 0xa5, 0x34, 0x95, 0xdb, 0x08, 0x33, 0x82, 0xa1, 0x00, 0xa3, 0x00, - 0xd8, 0x6f, 0x4b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, - 0x04, 0x03, 0x01, 0x76, 0x66, 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, - 0x67, 0x6e, 0x73, 0x72, 0x75, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, - 0x6c, 0x65, 0x03, 0x02, 0x81, 0xa1, 0x01, 0xa1, 0x02, 0x81, 0x82, 0x07, - 0x58, 0x30, 0x20, 0xff, 0x68, 0x1a, 0x08, 0x82, 0xe2, 0x9b, 0x48, 0x19, - 0x53, 0x88, 0x89, 0x36, 0x20, 0x9c, 0xb5, 0x3d, 0xf9, 0xc5, 0xaa, 0xec, - 0x60, 0x6a, 0x2c, 0x24, 0xa0, 0xfb, 0x13, 0x85, 0x95, 0x12, 0x4b, 0x8e, - 0x3f, 0x24, 0xa1, 0x27, 0x71, 0xbc, 0x38, 0x54, 0xcc, 0x68, 0xb4, 0x03, - 0x61, 0xad, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, - 0x48, 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x01, 0x01, 0x76, - 0x66, 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, - 0x75, 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x81, 0xa1, - 0x01, 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x58, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x58, 0x30, 0x46, - 0x62, 0x24, 0x34, 0x3d, 0x68, 0x18, 0x02, 0xc1, 0x50, 0x6b, 0xbe, 0xd7, - 0xd7, 0xf0, 0x0b, 0x96, 0x9b, 0xad, 0xdd, 0x63, 0x46, 0xe4, 0xf2, 0xe7, - 0xce, 0x14, 0x66, 0x92, 0x99, 0x6f, 0x22, 0xa4, 0x58, 0x14, 0xde, 0x81, - 0xd2, 0x48, 0xf5, 0x83, 0xb6, 0x5f, 0x81, 0x7b, 0x5f, 0xce, 0xab, 0x01, - 0x81, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, 0x48, - 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x02, 0x01, 0x76, 0x66, - 0x70, 0x67, 0x61, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x73, 0x72, 0x75, - 0x73, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x81, 0xa1, 0x01, - 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - } + //go:embed testcases/comid-design-cd.cbor + testComidDesignCD []byte - comid := Comid{} - err := comid.FromCBOR(in) - if err != nil { - fmt.Printf("FAIL: %v", err) - } else { - fmt.Println("OK") - } + //go:embed testcases/comid-firmware-cd.cbor + testComidFirmwareCD []byte - // Output: OK -} + //go:embed testcases/comid-3.cbor + testComid3 []byte -func Example_decode_CBOR_4() { - // https://github.com/ietf-rats/ietf-corim-cddl/blob/main/examples/comid-firmware-cd.diag - in := []byte{ - 0xa3, 0x01, 0xa1, 0x00, 0x50, 0xaf, 0x1c, 0xd8, 0x95, 0xbe, 0x78, 0x4a, - 0xdb, 0xb7, 0xe9, 0xad, 0xd4, 0x4a, 0x65, 0xab, 0xf3, 0x02, 0x81, 0xa3, - 0x00, 0x71, 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x20, 0x4d, - 0x46, 0x47, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x01, 0xd8, 0x20, 0x78, 0x18, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x77, 0x6d, 0x66, - 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, - 0x02, 0x81, 0x00, 0x04, 0xa2, 0x00, 0x82, 0x82, 0xa1, 0x00, 0xa4, 0x01, - 0x70, 0x66, 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x67, 0x66, 0x77, 0x59, 0x5f, 0x6e, - 0x35, 0x78, 0x03, 0x00, 0x04, 0x00, 0x81, 0xa1, 0x01, 0xa2, 0x01, 0xd9, - 0x02, 0x28, 0x01, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x15, 0xe7, 0x7d, - 0x6f, 0x13, 0x32, 0x52, 0xf1, 0xdb, 0x70, 0x44, 0x90, 0x13, 0x13, 0x88, - 0x4f, 0x29, 0x77, 0xd2, 0x10, 0x9b, 0x33, 0xc7, 0x9f, 0x33, 0xe0, 0x79, - 0xbf, 0xc7, 0x88, 0x65, 0x25, 0x5c, 0x0f, 0xb7, 0x33, 0xc2, 0x40, 0xfd, - 0xda, 0x54, 0x4b, 0x82, 0x15, 0xd7, 0xb8, 0xf8, 0x15, 0x82, 0xa1, 0x00, - 0xa4, 0x01, 0x70, 0x66, 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x67, 0x66, 0x77, 0x58, - 0x5f, 0x6e, 0x35, 0x78, 0x03, 0x01, 0x04, 0x00, 0x81, 0xa1, 0x01, 0xa2, - 0x01, 0xd9, 0x02, 0x28, 0x01, 0x02, 0x81, 0x82, 0x07, 0x58, 0x30, 0x3d, - 0x90, 0xb6, 0xbf, 0x00, 0x3d, 0xa2, 0xd9, 0x4e, 0xa5, 0x46, 0x3f, 0x97, - 0xfb, 0x3c, 0x53, 0xdd, 0xc5, 0x1c, 0xfb, 0xa1, 0xe3, 0xe3, 0x8e, 0xef, - 0x7a, 0xf0, 0x71, 0xa6, 0x79, 0x86, 0x59, 0x5d, 0x22, 0x72, 0x91, 0x31, - 0xdf, 0x9f, 0xe8, 0x0f, 0x54, 0x51, 0xee, 0xf1, 0x54, 0xf8, 0x5e, 0x01, - 0x81, 0x82, 0xa1, 0x00, 0xa2, 0x00, 0xd8, 0x6f, 0x4c, 0x60, 0x86, 0x48, - 0x01, 0x86, 0xf8, 0x4d, 0x01, 0x0f, 0x04, 0x63, 0x01, 0x01, 0x70, 0x66, - 0x77, 0x6d, 0x66, 0x67, 0x69, 0x6e, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x81, 0xa1, 0x01, 0xa2, 0x04, 0xd9, 0x02, 0x30, 0x48, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x48, 0xff, 0xff, - 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - } + //go:embed testcases/comid-4.cbor + testComid4 []byte - comid := Comid{} - err := comid.FromCBOR(in) - if err != nil { - fmt.Printf("FAIL: %v", err) - } else { - fmt.Println("OK") - } - - // Output: OK -} + //go:embed testcases/comid-5.cbor + testComid5 []byte +) -func Example_decode_CBOR_5() { - // Taken from https://github.com/ietf-corim-cddl/blob/main/examples/comid-3.diag - in := []byte{ - 0xa3, 0x01, 0xa1, 0x00, 0x78, 0x20, 0x6d, 0x79, 0x2d, 0x6e, 0x73, 0x3a, - 0x61, 0x63, 0x6d, 0x65, 0x2d, 0x72, 0x6f, 0x61, 0x64, 0x72, 0x75, 0x6e, - 0x6e, 0x65, 0x72, 0x2d, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x02, 0x81, 0xa3, 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, - 0x49, 0x6e, 0x63, 0x2e, 0x01, 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, 0x70, - 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x83, 0x01, 0x00, 0x02, 0x04, 0xa1, 0x00, - 0x81, 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd8, 0x6f, 0x44, 0x55, 0x02, 0xc0, - 0x00, 0x01, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x49, 0x6e, 0x63, 0x2e, - 0x02, 0x78, 0x18, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x52, 0x6f, 0x61, 0x64, - 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x20, 0x46, 0x69, 0x72, 0x6d, 0x77, - 0x61, 0x72, 0x65, 0x81, 0xa2, 0x00, 0x19, 0x02, 0xbc, 0x01, 0xa1, 0x02, - 0x81, 0x82, 0x06, 0x44, 0xab, 0xcd, 0xef, 0x00, +func TestExample_decode_CBOR(_ *testing.T) { + tvs := []struct { + descr string + inp []byte + }{ + { + descr: "Test with CoMID-1 Diag", + inp: testComid1, + }, + { + descr: "Test with CoMID-2 Diag", + inp: testComid2, + }, + { + descr: "Test with CoMID-Design-CD Diag", + inp: testComidDesignCD, + }, + { + descr: "Test with Firmware-CD Diag", + inp: testComidFirmwareCD, + }, + { + descr: "Test with CoMID-3 Diag", + inp: testComid3, + }, + { + descr: "Test with CoMID-4 Diag", + inp: testComid4, + }, + { + descr: "Test with CoMID-5 Diag", + inp: testComid5, + }, } - comid := Comid{} - err := comid.FromCBOR(in) - if err != nil { - fmt.Printf("FAIL: %v", err) - } else { - fmt.Println("OK") + for _, tv := range tvs { + comid := Comid{} + err := comid.FromCBOR(tv.inp) + if err != nil { + fmt.Printf("FAIL: %v", err) + } else { + fmt.Println("OK") + } + // Output: OK } - - // Output: OK } diff --git a/comid/measurement.go b/comid/measurement.go index ac626246..19777357 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -810,7 +810,6 @@ func (o Measurements) MarshalCBOR() ([]byte, error) { func (o *Measurements) UnmarshalCBOR(data []byte) error { return (*extensions.Collection[Measurement, *Measurement])(o).UnmarshalCBOR(data) } - func (o Measurements) MarshalJSON() ([]byte, error) { return (extensions.Collection[Measurement, *Measurement])(o).MarshalJSON() } diff --git a/comid/test_vars.go b/comid/test_vars.go index ce3b59fa..eb7a29ed 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -516,6 +516,6 @@ var ( ] } ] -} + } }` ) diff --git a/comid/testcases/comid-1.cbor b/comid/testcases/comid-1.cbor new file mode 100644 index 0000000000000000000000000000000000000000..58dba91b8d9867f87339f9acd912738fd4d5f350 GIT binary patch literal 175 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@C-vGA?3R#E@#JXP{@mDCxk!)Y!xrq2RL0IP1%4krkEBdck*73y=Sr UeP;dkc#oz?HQl_$OKwU70H=mNLjV8( literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-2.cbor b/comid/testcases/comid-2.cbor new file mode 100644 index 0000000000000000000000000000000000000000..4c8e045ce4036cc14c3a5b77c04a1fff95553855 GIT binary patch literal 454 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@t_Kyoh5R�u2HVZ6zt!3Y4K7o=4H literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-3.cbor b/comid/testcases/comid-3.cbor new file mode 100644 index 0000000000000000000000000000000000000000..4277e67af9c91f6d6145363a4c2f890c3356e6d9 GIT binary patch literal 152 zcmZ3?xR9YjA-7UDuh=RvIX6|eC_gc!s5CDxwMe(Pw4fj-H8(Y{gsE{cL#Cs%ud9M* zUa}tJ4TX}7l9GaAD}8;C9=+6x#9W|Cre;P4CYFT^jZF&~7Bk$)cL`-Wz`%&m$5bH! nHX{gVMG)8u1-Hzi-15Yt)W$^&l1zIT7cwho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@HWEGA?3R#E@#JXP{@mDCxk!)Y!xrq2RL0IP1%4krkEBdck*73y=Sr ceP;dkc#oz?HQl_$OKwU-bs3@R`cFU?0M6@F5&!@I literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-5.cbor b/comid/testcases/comid-5.cbor new file mode 100644 index 0000000000000000000000000000000000000000..f4f514c9e494ea35606852b35f90257fec08d809 GIT binary patch literal 233 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@HWEF)%VNVpznGYN%(RXTT`wz`)el#2BIAvdTE?%W07nmCky>cT)?G h|C)Vf{q}f|rbsp2yv0jyN-ttyg6K0s)%Tx(J^-QYR2KjM literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-design-cd.cbor b/comid/testcases/comid-design-cd.cbor new file mode 100644 index 0000000000000000000000000000000000000000..643199ad935af3acbfddf5126397cb222b3da197 GIT binary patch literal 612 zcmZ3&xR4=0Zq3zcUl#jso4J48);=yjcc#Y0425n1?v4sBsl}P;dBwUxx}n94Hxw%5 zGD=DcimmkZ(+biPQy`K>rNw%w6^Xe8IjKyI49ty-7y_n$UCu6(@7&*B<;BF_@cJbq z1Ir?YmZpUaiy3a@dndGcFt+{hW#ngJWGusO5HnNbLdHcbH<=7P7@&aF<3A99IGE-z z;WcL=Q)3f*gn|9TzEcjt?eiGxzP_%q3k*-46F+-nbS>x9+1+jKO zx__z3)Y}}!nAR{8V~xW93@MJLN3%U7gF8CS6y|KT{dsiNn}jSKl?A_rTc-+n_t~o~ zR4?3P5ppJD3v=RHpf!ttq2hxGm1La30t=J~16ZgKOS488xFxBW*k(vD9Sq3ccm4VY z?rF2v-c5FU^6B|Gk+exO^OcrFh}>(u?O(qRSrp6}r2m{gQwfVwEK_71yxJ(ch?(o+vza%i*`1pL|2bKGecciMu@NYLh z /dev/null && pwd ) + +if [[ "$(type -p diag2cbor.rb)" == "" ]]; then + echo "ERROR: please install ruby-cbor-diag package" + exit 1 +fi + +for case in "$THIS_DIR"/src/*.diag; do + outfile=$(basename "${case%%.diag}").cbor + + echo "generating $outfile" + + diag2cbor.rb "$case" > "$THIS_DIR/$outfile" +done + +echo "done." diff --git a/comid/testcases/src/comid-1.diag b/comid/testcases/src/comid-1.diag new file mode 100644 index 00000000..759617b5 --- /dev/null +++ b/comid/testcases/src/comid-1.diag @@ -0,0 +1,39 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ]] + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-2.diag b/comid/testcases/src/comid-2.diag new file mode 100644 index 00000000..f5309a0f --- /dev/null +++ b/comid/testcases/src/comid-2.diag @@ -0,0 +1,105 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner Firmware", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ] + ], + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'a71b3e388d454a0581f352e58c832c5c' + ), + / comid.vendor / 1 : "WYLIE Inc.", + / comid.model / 2 : "WYLIE Coyote Trusted OS", + / comid.layer / 3 : 2, + / comid.index / 4 : 0 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'bb71198ed60a95dc3c619e555c2c0b8d7564a38031b034a195892591c65365b0' + ] ] + } + } + ] + ], + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'a71b3e388d454a0581f352e58c832c5c' + ), + / comid.vendor / 1 : "WYLIE Inc.", + / comid.model / 2 : "WYLIE Coyote Trusted OS", + / comid.layer / 3 : 2, + / comid.index / 4 : 1 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'bb71198ed60a95dc3c619e555c2c0b8d7564a38031b034a195892591c65365b0' + ] ] + } + } + ] + ] + ], + / comid.endorsed-triples / 1 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME Root of Trust", + / comid.layer / 3 : 0 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1) + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-3.diag b/comid/testcases/src/comid-3.diag new file mode 100644 index 00000000..ec0e66d3 --- /dev/null +++ b/comid/testcases/src/comid-3.diag @@ -0,0 +1,36 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : "my-ns:acme-roadrunner-supplement" + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 1,0,2 ] / creator, tag-creator, maintainer / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111( + h'5502C000' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner Firmware" + } + }, + [ + / measurement-map / { + / comid.mkey / 0: 700, + / comid.mval / 1 : { + / comid.digests / 2 : [[ + / hash-alg-id / 6, / sha-256-32 / + / hash-value / h'ABCDEF00' ]] + } + } + ] + ] + ] + } +} diff --git a/comid/testcases/src/comid-4.diag b/comid/testcases/src/comid-4.diag new file mode 100644 index 00000000..9c3cd9f4 --- /dev/null +++ b/comid/testcases/src/comid-4.diag @@ -0,0 +1,51 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map A / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + }, + / measurement-map B / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "2.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'FFaa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ] + ] ] + } +} \ No newline at end of file diff --git a/comid/testcases/src/comid-5.diag b/comid/testcases/src/comid-5.diag new file mode 100644 index 00000000..b9792475 --- /dev/null +++ b/comid/testcases/src/comid-5.diag @@ -0,0 +1,53 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map A / { + /comid.mkey / 0 : 1, + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + }, + / measurement-map B / { + /comid.mkey / 0 : 2, + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "2.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'FFaa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ] + ] ] + } +} \ No newline at end of file diff --git a/comid/testcases/src/comid-design-cd.diag b/comid/testcases/src/comid-design-cd.diag new file mode 100644 index 00000000..cae08c4c --- /dev/null +++ b/comid/testcases/src/comid-design-cd.diag @@ -0,0 +1,129 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'1EACD596F4A34FB699BFAEB58E0A4E47' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "FPGA Designs-R-Us", + / comid.reg-id / 1 : 32("https://fpgadesignsrus.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.linked-tags / 3 : [ { + / comid.linked-tag-id / 0 : h'97F5A7071C6F438F877A4A020780EBE9', + / comid.tag-rel / 1 : / comid.supplements / 0 + } + ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / ** Layer 3 device state ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0401'), / 2.16.840.1.113741.1.15.4.1 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + [ + / measurement-map / + { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' + } + } + ] + ], + [ + / environment-map / { + / ** Layer 2 design (IO descriptor) hash ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0402'), / 2.16.840.1.113741.1.15.4.2 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + [ + / measurement-map / + { + / comid.mval / 1 : { + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'3FE18ECA4053879E017EF5EB7A3E5157659C5F9BB15B7D09959B8B8647822A4CC21C3AA6721CEF87F5BFA53495DB0833' + ] + ] + } + } + ] + ], + [ + / environment-map / { + / ** Layer 2 design (CORE descriptor) hash ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0403'), / 2.16.840.1.113741.1.15.4.3 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + [ + / measurement-map / + { + / comid.mval / 1 : { + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'20FF681A0882E29B481953888936209CB53DF9C5AAEC606A2C24A0FB138595124B8E3F24A12771BC3854CC68B40361AD' + ] + ] + } + } + ] + ], + [ + / environment-map / { + / ** Firmware is valid (example assertion) ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "fpgadesignsrus.example" + } + }, + [ + / measurement-map / + { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), + / comid.raw-value-mask / 5 : h'466224343D681802C1506BBED7D7F00B969BADDD6346E4F2E7CE146692996F22A45814DE81D248F583B65F817B5FCEAB' + } + } + ] + ] + ], + / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / ** Design is valid (example assertion) ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046302'), / 2.16.840.1.113741.1.15.4.99.2 / + / comid.vendor / 1 : "fpgadesignsrus.example" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' + } + } + ] + ] + ] + } + } diff --git a/comid/testcases/src/comid-firmware-cd.diag b/comid/testcases/src/comid-firmware-cd.diag new file mode 100644 index 00000000..d61ab7db --- /dev/null +++ b/comid/testcases/src/comid-firmware-cd.diag @@ -0,0 +1,86 @@ + + / concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'AF1CD895BE784ADBB7E9ADD44A65ABF3' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "Firmware MFG Inc.", + / comid.reg-id / 1 : 32("https://fwmfginc.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / ** Hash of layer 0 firmware ** / + / comid.class / 0 : { + / comid.vendor / 1 : "fwmfginc.example", + / comid.model / 2 : "fwY_n5x", + / comid.layer / 3 : 0, + / comid.index / 4 : 0 + } + }, + [ + / measurement-map / + { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1), + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'15E77D6F133252F1DB7044901313884F2977D2109B33C79F33E079BFC78865255C0FB733C240FDDA544B8215D7B8F815' + ] + ] + } + } + ] + ], + [ + / environment-map / { + / ** Hash of layer 1 firmware ** / + / comid.class / 0 : { + / comid.vendor / 1 : "fwmfginc.example", + / comid.model / 2 : "fwX_n5x", + / comid.layer / 3 : 1, + / comid.index / 4 : 0 + } + }, + [ + / measurement-map / + { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1), + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'3D90B6BF003DA2D94EA5463F97FB3C53DDC51CFBA1E3E38EEF7AF071A67986595D22729131DF9FE80F5451EEF154F85E' + ] + ] + } + } + ] + ] + ], + / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / comid.class / 0 : { + / ** Firmware is valid (example) ** / + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "fwmfginc.example" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' + } + } + ] + ] + ] + } + } From 662141409fb0e4e1c61b8e5dfebe897633912635 Mon Sep 17 00:00:00 2001 From: Dionna Glaze Date: Tue, 22 Oct 2024 23:19:34 +0000 Subject: [PATCH 054/110] Cut #6.500, #6.502 in FromCOSE Top level tags are not interpretable as a COSE object. The protected header content-type is not meant to be application/rim+cbor according to the spec draft (Issue #132). Support the spec's optional unsigned corim tag in COSE payload. Signed-off-by: Dionna Glaze --- corim/cbor.go | 8 ++-- corim/signedcorim.go | 15 +++++- corim/signedcorim_test.go | 98 +++++++++++++++++++++++++++++++++++++-- corim/unsignedcorim.go | 2 + 4 files changed, 116 insertions(+), 7 deletions(-) diff --git a/corim/cbor.go b/corim/cbor.go index 846759ce..75ed1757 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -17,11 +17,13 @@ var ( ) var ( - CoswidTag = []byte{0xd9, 0x01, 0xf9} // 505() - ComidTag = []byte{0xd9, 0x01, 0xfa} // 506() + UnsignedCorimTag = []byte{0xd9, 0x01, 0xf5} // 501() + CoswidTag = []byte{0xd9, 0x01, 0xf9} // 505() + ComidTag = []byte{0xd9, 0x01, 0xfa} // 506() corimTagsMap = map[uint64]interface{}{ - 32: comid.TaggedURI(""), + 32: comid.TaggedURI(""), + 501: TaggedUnsignedCorim(UnsignedCorim{}), } ) diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 3d7b2158..c1b5e919 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -4,6 +4,7 @@ package corim import ( + "bytes" "crypto" "crypto/rand" "errors" @@ -15,7 +16,8 @@ import ( ) var ( - ContentType = "application/rim+cbor" + ContentType = "application/corim-unsigned+cbor" + oldContentType = "application/rim+cbor" NoExternalData = []byte("") HeaderLabelCorimMeta = int64(8) ) @@ -63,6 +65,11 @@ func (o *SignedCorim) processHdrs() error { return errors.New("missing mandatory content type") } + // Compatibility step from older spec draft + if v == oldContentType { + v = ContentType + } + if v != ContentType { return fmt.Errorf("expecting content type %q, got %q instead", ContentType, v) } @@ -100,6 +107,12 @@ func (o *SignedCorim) processHdrs() error { func (o *SignedCorim) FromCOSE(buf []byte) error { o.message = cose.NewSign1Message() + // If a tagged-corim-type-choice #6.500 of tagged-signed-corim #6.502, strip the prefix. + // This is a remnant of an older draft of the specification before + // https://github.com/ietf-rats-wg/draft-ietf-rats-corim/pull/337 + corimTypeChoice := []byte("\xd9\x01\xf4\xd9\x01\xf6") + buf, _ = bytes.CutPrefix(buf, corimTypeChoice) + if err := o.message.UnmarshalCBOR(buf); err != nil { return fmt.Errorf("failed CBOR decoding for COSE-Sign1 signed CoRIM: %w", err) } diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 290555e2..34a03367 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -177,6 +177,98 @@ func TestSignedCorim_FromCOSE_ok(t *testing.T) { assert.Nil(t, err) } +func TestSignedCorim_TaggedFromCOSE_ok(t *testing.T) { + /* + 500( + 502( + 18( + [ + / protected h'a10126' / << { + / alg / 1: -7, / ECDSA 256 / + / content-type / 3: "application/rim+cbor", + / issuer-key-id / 4: 'meriadoc.brandybuck@buckland.example', + / corim-meta / 8: h'a200a1006941434d45204c74642e01a101c11a5fad2056' + } >>, + / unprotected / {}, + / payload / << 501({ + 0: "test corim id", + 1: [ + h'D901FAA40065656E2D474201A1005043BBE37F2E614B33AED353CFF1428B160281A3006941434D45204C74642E01D8207468747470733A2F2F61636D652E6578616D706C65028300010204A1008182A100A300D90258582061636D652D696D706C656D656E746174696F6E2D69642D303030303030303031016441434D45026A526F616452756E6E657283A200D90258A30162424C0465322E312E30055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A102818201582087428FC522803D31065E7BCE3CF03FE475096631E5E07BBD7A0FDE60C4CF25C7A200D90258A3016450526F540465312E332E35055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A10281820158200263829989B6FD954F72BAAF2FC64BC2E2F01D692D4DE72986EA808F6E99813FA200D90258A3016441526F540465302E312E34055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A1028182015820A3A5E715F0CC574A73C3F9BEBB6BC24F32FFD5B67B387244C2C909DA779A1478' + ] + }) >>, + / signature / h'deadbeef' + ] + ))) + */ + tv := []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6, 0xd2, 0x84, + 0x58, 0x59, 0xa4, 0x01, 0x26, 0x03, 0x74, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2f, 0x72, 0x69, 0x6d, 0x2b, 0x63, 0x62, 0x6f, 0x72, + 0x04, 0x58, 0x24, 0x6d, 0x65, 0x72, 0x69, 0x61, 0x64, + 0x6f, 0x63, 0x2e, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x79, + 0x62, 0x75, 0x63, 0x6b, 0x40, 0x62, 0x75, 0x63, 0x6b, + 0x6c, 0x61, 0x6e, 0x64, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x08, 0x57, 0xa2, 0x00, 0xa1, 0x00, + 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x4c, 0x74, 0x64, + 0x2e, 0x01, 0xa1, 0x01, 0xc1, 0x1a, 0x5f, 0xad, 0x20, + 0x56, 0xa0, 0x59, 0x01, 0xbb, 0xd9, 0x01, 0xf5, 0xa2, + 0x00, 0x6d, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6f, + 0x72, 0x69, 0x6d, 0x20, 0x69, 0x64, 0x01, 0x81, 0x59, + 0x01, 0xa3, 0xd9, 0x01, 0xfa, 0xa4, 0x00, 0x65, 0x65, + 0x6e, 0x2d, 0x47, 0x42, 0x01, 0xa1, 0x00, 0x50, 0x43, + 0xbb, 0xe3, 0x7f, 0x2e, 0x61, 0x4b, 0x33, 0xae, 0xd3, + 0x53, 0xcf, 0xf1, 0x42, 0x8b, 0x16, 0x02, 0x81, 0xa3, + 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x4c, 0x74, + 0x64, 0x2e, 0x01, 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, + 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x63, 0x6d, 0x65, + 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, + 0x83, 0x00, 0x01, 0x02, 0x04, 0xa1, 0x00, 0x81, 0x82, + 0xa1, 0x00, 0xa3, 0x00, 0xd9, 0x02, 0x58, 0x58, 0x20, + 0x61, 0x63, 0x6d, 0x65, 0x2d, 0x69, 0x6d, 0x70, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2d, 0x69, 0x64, 0x2d, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x31, 0x01, 0x64, 0x41, 0x43, + 0x4d, 0x45, 0x02, 0x6a, 0x52, 0x6f, 0x61, 0x64, 0x52, + 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x83, 0xa2, 0x00, 0xd9, + 0x02, 0x58, 0xa3, 0x01, 0x62, 0x42, 0x4c, 0x04, 0x65, + 0x32, 0x2e, 0x31, 0x2e, 0x30, 0x05, 0x58, 0x20, 0xac, + 0xbb, 0x11, 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, 0x52, + 0x3c, 0xe4, 0xce, 0x1a, 0x24, 0x5a, 0xe1, 0xa2, 0x39, + 0xae, 0x3c, 0x6b, 0xfd, 0x9e, 0x78, 0x71, 0xf7, 0xe5, + 0xd8, 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, 0x81, 0x82, + 0x01, 0x58, 0x20, 0x87, 0x42, 0x8f, 0xc5, 0x22, 0x80, + 0x3d, 0x31, 0x06, 0x5e, 0x7b, 0xce, 0x3c, 0xf0, 0x3f, + 0xe4, 0x75, 0x09, 0x66, 0x31, 0xe5, 0xe0, 0x7b, 0xbd, + 0x7a, 0x0f, 0xde, 0x60, 0xc4, 0xcf, 0x25, 0xc7, 0xa2, + 0x00, 0xd9, 0x02, 0x58, 0xa3, 0x01, 0x64, 0x50, 0x52, + 0x6f, 0x54, 0x04, 0x65, 0x31, 0x2e, 0x33, 0x2e, 0x35, + 0x05, 0x58, 0x20, 0xac, 0xbb, 0x11, 0xc7, 0xe4, 0xda, + 0x21, 0x72, 0x05, 0x52, 0x3c, 0xe4, 0xce, 0x1a, 0x24, + 0x5a, 0xe1, 0xa2, 0x39, 0xae, 0x3c, 0x6b, 0xfd, 0x9e, + 0x78, 0x71, 0xf7, 0xe5, 0xd8, 0xba, 0xe8, 0x6b, 0x01, + 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x02, 0x63, + 0x82, 0x99, 0x89, 0xb6, 0xfd, 0x95, 0x4f, 0x72, 0xba, + 0xaf, 0x2f, 0xc6, 0x4b, 0xc2, 0xe2, 0xf0, 0x1d, 0x69, + 0x2d, 0x4d, 0xe7, 0x29, 0x86, 0xea, 0x80, 0x8f, 0x6e, + 0x99, 0x81, 0x3f, 0xa2, 0x00, 0xd9, 0x02, 0x58, 0xa3, + 0x01, 0x64, 0x41, 0x52, 0x6f, 0x54, 0x04, 0x65, 0x30, + 0x2e, 0x31, 0x2e, 0x34, 0x05, 0x58, 0x20, 0xac, 0xbb, + 0x11, 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, 0x52, 0x3c, + 0xe4, 0xce, 0x1a, 0x24, 0x5a, 0xe1, 0xa2, 0x39, 0xae, + 0x3c, 0x6b, 0xfd, 0x9e, 0x78, 0x71, 0xf7, 0xe5, 0xd8, + 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, 0x81, 0x82, 0x01, + 0x58, 0x20, 0xa3, 0xa5, 0xe7, 0x15, 0xf0, 0xcc, 0x57, + 0x4a, 0x73, 0xc3, 0xf9, 0xbe, 0xbb, 0x6b, 0xc2, 0x4f, + 0x32, 0xff, 0xd5, 0xb6, 0x7b, 0x38, 0x72, 0x44, 0xc2, + 0xc9, 0x09, 0xda, 0x77, 0x9a, 0x14, 0x78, 0x44, 0xde, + 0xad, 0xbe, 0xef} + + var actual SignedCorim + err := actual.FromCOSE(tv) + + assert.Nil(t, err) +} + func TestSignedCorim_FromCOSE_fail_no_tag(t *testing.T) { // a single null byte is sufficient to test this condition because the tag // is the very first thing we stumble upon @@ -193,7 +285,7 @@ func TestSignedCorim_FromCOSE_fail_corim_bad_cbor(t *testing.T) { [ / protected / << { / alg / 1: -7, / ECDSA 256 / - / content-type / 3: "application/rim+cbor", + / content-type / 3: "application/corim-unsigned+cbor", / corim-meta / 8: h'a200a1006941434d45204c74642e01a101c11a5fad2056' } >>, / unprotected / {}, @@ -223,7 +315,7 @@ func TestSignedCorim_FromCOSE_fail_invalid_corim(t *testing.T) { [ / protected / << { / alg / 1: -7, / ECDSA 256 / - / content-type / 3: "application/rim+cbor", + / content-type / 3: "application/corim-unsigned+cbor", / corim-meta / 8: h'a200a1006941434d45204c74642e01a101c11a5fad2056' } >>, / unprotected / {}, @@ -304,7 +396,7 @@ func TestSignedCorim_FromCOSE_fail_unexpected_content_type(t *testing.T) { var actual SignedCorim err := actual.FromCOSE(tv) - assert.EqualError(t, err, `processing COSE headers: expecting content type "application/rim+cbor", got "application/cbor" instead`) + assert.EqualError(t, err, `processing COSE headers: expecting content type "application/corim-unsigned+cbor", got "application/cbor" instead`) } func unsignedCorimFromCBOR(t *testing.T, cbor []byte) *UnsignedCorim { diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 06a6307f..bcaa7ece 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -38,6 +38,8 @@ type UnsignedCorim struct { Extensions } +type TaggedUnsignedCorim UnsignedCorim + // NewUnsignedCorim instantiates an empty UnsignedCorim func NewUnsignedCorim() *UnsignedCorim { return &UnsignedCorim{} From 868324ac2766ce1c6d5fe20601df943096a3afb6 Mon Sep 17 00:00:00 2001 From: Dionna Glaze Date: Tue, 24 Dec 2024 03:03:58 +0000 Subject: [PATCH 055/110] Revert content type following PR337 discussion. Signed-off-by: Dionna Glaze --- corim/signedcorim.go | 8 +------- corim/signedcorim_test.go | 6 +++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/corim/signedcorim.go b/corim/signedcorim.go index c1b5e919..abe12364 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -16,8 +16,7 @@ import ( ) var ( - ContentType = "application/corim-unsigned+cbor" - oldContentType = "application/rim+cbor" + ContentType = "application/rim+cbor" NoExternalData = []byte("") HeaderLabelCorimMeta = int64(8) ) @@ -65,11 +64,6 @@ func (o *SignedCorim) processHdrs() error { return errors.New("missing mandatory content type") } - // Compatibility step from older spec draft - if v == oldContentType { - v = ContentType - } - if v != ContentType { return fmt.Errorf("expecting content type %q, got %q instead", ContentType, v) } diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 34a03367..2e286489 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -285,7 +285,7 @@ func TestSignedCorim_FromCOSE_fail_corim_bad_cbor(t *testing.T) { [ / protected / << { / alg / 1: -7, / ECDSA 256 / - / content-type / 3: "application/corim-unsigned+cbor", + / content-type / 3: "application/rim+cbor", / corim-meta / 8: h'a200a1006941434d45204c74642e01a101c11a5fad2056' } >>, / unprotected / {}, @@ -315,7 +315,7 @@ func TestSignedCorim_FromCOSE_fail_invalid_corim(t *testing.T) { [ / protected / << { / alg / 1: -7, / ECDSA 256 / - / content-type / 3: "application/corim-unsigned+cbor", + / content-type / 3: "application/rim+cbor", / corim-meta / 8: h'a200a1006941434d45204c74642e01a101c11a5fad2056' } >>, / unprotected / {}, @@ -396,7 +396,7 @@ func TestSignedCorim_FromCOSE_fail_unexpected_content_type(t *testing.T) { var actual SignedCorim err := actual.FromCOSE(tv) - assert.EqualError(t, err, `processing COSE headers: expecting content type "application/corim-unsigned+cbor", got "application/cbor" instead`) + assert.EqualError(t, err, `processing COSE headers: expecting content type "application/rim+cbor", got "application/cbor" instead`) } func unsignedCorimFromCBOR(t *testing.T, cbor []byte) *UnsignedCorim { From e6b0fdc3e1998fb7e2aac2abc36b20c49b99a53d Mon Sep 17 00:00:00 2001 From: Priyanshu Thapliyal Date: Tue, 24 Dec 2024 17:20:52 +0530 Subject: [PATCH 056/110] fix(measurement): enhance Valid method to check Extensions for measurement values Signed-off-by: Priyanshu Thapliyal --- comid/measurement.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/comid/measurement.go b/comid/measurement.go index 19777357..45fd1821 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -424,6 +424,7 @@ func (o Mval) MarshalJSON() ([]byte, error) { } func (o Mval) Valid() error { + // Check if all base types are nil and Extensions are not nil if o.Ver == nil && o.SVN == nil && o.Digests == nil && @@ -436,7 +437,10 @@ func (o Mval) Valid() error { o.UEID == nil && o.UUID == nil && o.IntegrityRegisters == nil { - return fmt.Errorf("no measurement value set") + + if o.Extensions.IsEmpty() { + return fmt.Errorf("no measurement value set") + } } if o.Ver != nil { From e5debd3702bae17f9f69cd183fbcf776a8105686 Mon Sep 17 00:00:00 2001 From: Priyanshu Thapliyal Date: Wed, 25 Dec 2024 14:57:21 +0530 Subject: [PATCH 057/110] fix(measurement): streamline Valid method to check Extensions for measurement values Signed-off-by: Priyanshu Thapliyal --- comid/measurement.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/comid/measurement.go b/comid/measurement.go index 45fd1821..ef1d23dd 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -436,11 +436,9 @@ func (o Mval) Valid() error { o.SerialNumber == nil && o.UEID == nil && o.UUID == nil && - o.IntegrityRegisters == nil { - - if o.Extensions.IsEmpty() { - return fmt.Errorf("no measurement value set") - } + o.IntegrityRegisters == nil && + o.Extensions.IsEmpty() { + return fmt.Errorf("no measurement value set") } if o.Ver != nil { From 748c338ba80602075c0ed7fa71b578ace3c428c1 Mon Sep 17 00:00:00 2001 From: Priyanshu Thapliyal Date: Wed, 25 Dec 2024 15:10:23 +0530 Subject: [PATCH 058/110] Replaced nested ifs with conjunctions. Signed-off-by: Priyanshu Thapliyal --- comid/measurement.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/comid/measurement.go b/comid/measurement.go index ef1d23dd..b8b15ace 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -423,8 +423,8 @@ func (o Mval) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } +// Valid returns an error if none of the measurement values are set and the Extensions are empty. func (o Mval) Valid() error { - // Check if all base types are nil and Extensions are not nil if o.Ver == nil && o.SVN == nil && o.Digests == nil && @@ -437,8 +437,8 @@ func (o Mval) Valid() error { o.UEID == nil && o.UUID == nil && o.IntegrityRegisters == nil && - o.Extensions.IsEmpty() { - return fmt.Errorf("no measurement value set") + o.Extensions.IsEmpty() { + return fmt.Errorf("no measurement value set") } if o.Ver != nil { From a1484904ea5a2b32e44db39cb6636c11b828ad04 Mon Sep 17 00:00:00 2001 From: "Akhilesh Kr. Yadav" Date: Mon, 6 Jan 2025 19:37:01 +0530 Subject: [PATCH 059/110] readme-update Signed-off-by: Akhilesh Kr. Yadav --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 36ff7bd6..d118c42f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ The [`corim/corim`](corim) and [`corim/comid`](comid) packages provide a golang > These API are still in active development (as is the underlying CoRIM spec). > They are **subject to change** in the future. +## Required Tools + +Ensure you have the following tools installed with the specified versions on your machine to ensure everything works properly: + +- **Go**: Version 1.22 +- **golangci-lint**: Version 1.54.2 + ## Developer tips Before requesting a PR (and routinely during the dev/test cycle), you are encouraged to run: From ca718d8ee90e018ff274edefa87291ff4dbcb7f1 Mon Sep 17 00:00:00 2001 From: "Akhilesh Kr." <2827daya@gmail.com> Date: Wed, 8 Jan 2025 21:46:31 +0530 Subject: [PATCH 060/110] refactor: put SVN conversion logic into a separate function Refactor SVN conversion logic into a separate function. Signed-off-by: Akhilesh Kr. Yadav --- comid/svn.go | 98 +++++++++++++++++++++-------------------------- comid/svn_test.go | 4 +- 2 files changed, 46 insertions(+), 56 deletions(-) diff --git a/comid/svn.go b/comid/svn.go index 6d634a2f..692fad18 100644 --- a/comid/svn.go +++ b/comid/svn.go @@ -113,34 +113,11 @@ func NewTaggedSVN(val any) (*SVN, error) { return &SVN{&ret}, nil } - switch t := val.(type) { - case string: - u, err := strconv.ParseUint(t, 10, 64) - if err != nil { - return nil, err - } - ret = TaggedSVN(u) - case TaggedSVN: - ret = t - case *TaggedSVN: - ret = *t - case uint64: - ret = TaggedSVN(t) - case uint: - ret = TaggedSVN(t) - case int: - if t < 0 { - return nil, fmt.Errorf("SVN cannot be negative: %d", t) - } - ret = TaggedSVN(t) - case int64: - if t < 0 { - return nil, fmt.Errorf("SVN cannot be negative: %d", t) - } - ret = TaggedSVN(t) - default: - return nil, fmt.Errorf("unexpected type for SVN exact-value: %T", t) + u, err := convertToSVNUint64(val) + if err != nil { + return nil, err } + ret = TaggedSVN(u) return &SVN{&ret}, nil } @@ -175,34 +152,11 @@ func NewTaggedMinSVN(val any) (*SVN, error) { return &SVN{&ret}, nil } - switch t := val.(type) { - case string: - u, err := strconv.ParseUint(t, 10, 64) - if err != nil { - return nil, err - } - ret = TaggedMinSVN(u) - case TaggedMinSVN: - ret = t - case *TaggedMinSVN: - ret = *t - case uint64: - ret = TaggedMinSVN(t) - case uint: - ret = TaggedMinSVN(t) - case int: - if t < 0 { - return nil, fmt.Errorf("SVN cannot be negative: %d", t) - } - ret = TaggedMinSVN(t) - case int64: - if t < 0 { - return nil, fmt.Errorf("SVN cannot be negative: %d", t) - } - ret = TaggedMinSVN(t) - default: - return nil, fmt.Errorf("unexpected type for SVN min-value: %T", t) + u, err := convertToSVNUint64(val) + if err != nil { + return nil, err } + ret = TaggedMinSVN(u) return &SVN{&ret}, nil } @@ -228,6 +182,42 @@ func (o TaggedMinSVN) Valid() error { return nil } +// convertToSVNUint64 converts various SVN types to uint64. +func convertToSVNUint64(val any) (uint64, error) { + switch t := val.(type) { + case string: + u, err := strconv.ParseUint(t, 10, 64) + if err != nil { + return 0, err + } + return u, nil + case uint64: + return t, nil + case uint: + return uint64(t), nil + case int: + if t < 0 { + return 0, fmt.Errorf("SVN cannot be negative: %d", t) + } + return uint64(t), nil + case int64: + if t < 0 { + return 0, fmt.Errorf("SVN cannot be negative: %d", t) + } + return uint64(t), nil + case TaggedSVN: + return uint64(t), nil + case *TaggedSVN: + return uint64(*t), nil + case TaggedMinSVN: + return uint64(t), nil + case *TaggedMinSVN: + return uint64(*t), nil + default: + return 0, fmt.Errorf("unexpected type for SVN: %T", t) + } +} + // ISVNFactory defines the signature for the factory functions that may be // registred using RegisterSVNType to provide a new implementation of the // corresponding type choice. The factory function should create a new *SVN diff --git a/comid/svn_test.go b/comid/svn_test.go index 6127e0bf..b9d29ad6 100644 --- a/comid/svn_test.go +++ b/comid/svn_test.go @@ -103,7 +103,7 @@ func Test_NewSVN(t *testing.T) { assert.NoError(t, err) _, err = NewSVN(true, "exact-value") - assert.EqualError(t, err, "unexpected type for SVN exact-value: bool") + assert.EqualError(t, err, "unexpected type for SVN: bool") inMin := TaggedMinSVN(7) @@ -114,7 +114,7 @@ func Test_NewSVN(t *testing.T) { assert.NoError(t, err) _, err = NewSVN(true, "min-value") - assert.EqualError(t, err, "unexpected type for SVN min-value: bool") + assert.EqualError(t, err, "unexpected type for SVN: bool") _, err = NewSVN(true, "test") assert.EqualError(t, err, "unknown SVN type: test") From 8f38e7468ed904fdab5d1c686076c9c9298c4099 Mon Sep 17 00:00:00 2001 From: ravjot07 <138920477+ravjot07@users.noreply.github.com> Date: Sat, 11 Jan 2025 16:15:29 +0530 Subject: [PATCH 061/110] feat: add validation for MAC and IP addresses in Measurement.Valid method Also: * enhance validation logic in Mval.Valid() * add and refine tests for Mval.Valid() function * update MAC address validation to allow EUI-48 and EUI-64 formats --------- Signed-off-by: Ravjot Singh --- comid/measurement.go | 24 +++++++++- comid/measurement_test.go | 97 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/comid/measurement.go b/comid/measurement.go index 19777357..c64909f8 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -424,6 +424,7 @@ func (o Mval) MarshalJSON() ([]byte, error) { } func (o Mval) Valid() error { + // Check if no measurement values are set if o.Ver == nil && o.SVN == nil && o.Digests == nil && @@ -439,28 +440,47 @@ func (o Mval) Valid() error { return fmt.Errorf("no measurement value set") } + // Validate Version if o.Ver != nil { if err := o.Ver.Valid(); err != nil { return err } } + // Validate Digests if o.Digests != nil { if err := o.Digests.Valid(); err != nil { return err } } + // Validate Flags if o.Flags != nil { if err := o.Flags.Valid(); err != nil { return err } } - // raw value and mask have no specific semantics + // Validate MAC Address + if o.MACAddr != nil { + macLen := len(*o.MACAddr) + if macLen != 6 && macLen != 8 { // MAC address must be either 6 or 8 bytes + return fmt.Errorf("invalid MAC address length: expected 6 or 8 bytes, got %d", macLen) + } + } + + // Validate IP Address + if o.IPAddr != nil { + ip := *o.IPAddr + // Must be valid IPv4 or IPv6 (i.e., .To4() != nil or .To16() != nil) + if ip.To4() == nil && ip.To16() == nil { + return fmt.Errorf("invalid IP address: %s", ip.String()) + } + } - // TODO(tho) MAC addr & friends (see https://github.com/veraison/corim/issues/18) + // raw value and raw-value-mask have no specific semantics here + // Validate extensions (custom logic implemented in validMval()) return o.Extensions.validMval(&o) } diff --git a/comid/measurement_test.go b/comid/measurement_test.go index 078c7ee9..45505ea2 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -6,6 +6,7 @@ package comid import ( "crypto" "fmt" + "net" "testing" "github.com/stretchr/testify/assert" @@ -601,3 +602,99 @@ func TestMkey_UintMkey(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 7, ret) } + +func TestMval_Valid(t *testing.T) { + t.Run("No fields set", func(t *testing.T) { + mval := Mval{} + err := mval.Valid() + assert.EqualError(t, err, "no measurement value set") + }) + + t.Run("All fields nil except Ver, which is valid", func(t *testing.T) { + var scheme swid.VersionScheme + _ = scheme.SetCode(swid.VersionSchemeSemVer) + mval := Mval{ + Ver: &Version{ + Version: "1.0", + Scheme: scheme, + }, + } + err := mval.Valid() + assert.NoError(t, err) + }) + + // Test with valid 6-byte MAC + t.Run("MACAddr valid (6 bytes)", func(t *testing.T) { + mac := MACaddr([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) // EUI-48 + mval := Mval{MACAddr: &mac} + err := mval.Valid() + assert.NoError(t, err, "6-byte MAC should be valid") + }) + + // Test with valid 8-byte MAC + t.Run("MACAddr valid (8 bytes)", func(t *testing.T) { + mac := MACaddr([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}) // EUI-64 + mval := Mval{MACAddr: &mac} + err := mval.Valid() + assert.NoError(t, err, "8-byte MAC should be valid") + }) + + // Test with invalid MAC length + t.Run("MACAddr invalid (too many bytes)", func(t *testing.T) { + mac := MACaddr([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}) // 7 bytes + mval := Mval{MACAddr: &mac} + err := mval.Valid() + assert.EqualError(t, err, "invalid MAC address length: expected 6 or 8 bytes, got 7") + }) + + // Test with invalid MAC length + t.Run("MACAddr invalid (too few bytes)", func(t *testing.T) { + mac := MACaddr([]byte{0x01, 0x02, 0x03, 0x04}) // 4 bytes + mval := Mval{MACAddr: &mac} + err := mval.Valid() + assert.EqualError(t, err, "invalid MAC address length: expected 6 or 8 bytes, got 4") + }) + + t.Run("MACAddr valid (6 bytes)", func(t *testing.T) { + mac := MACaddr([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) + mval := Mval{MACAddr: &mac} + err := mval.Valid() + assert.NoError(t, err) + }) + + t.Run("IPAddr valid (IPv4)", func(t *testing.T) { + ip := net.IPv4(192, 168, 1, 100) + mval := Mval{IPAddr: &ip} + err := mval.Valid() + assert.NoError(t, err) + }) + + t.Run("IPAddr valid (IPv6)", func(t *testing.T) { + ip := net.ParseIP("2001:db8::1") + mval := Mval{IPAddr: &ip} + err := mval.Valid() + assert.NoError(t, err) + }) + + t.Run("Digests valid", func(t *testing.T) { + ds := NewDigests() + _ = ds.AddDigest(swid.Sha256, []byte{0xAA, 0xBB}) + mval := Mval{ + Digests: ds, + } + err := mval.Valid() + assert.NoError(t, err) + }) + + t.Run("Extensions valid", func(t *testing.T) { + // Suppose we have some extension data that is considered valid + ext := Extensions{} + mval := Mval{ + Extensions: ext, + // Must also set one non-empty field to pass "no measurement value set" + Ver: &Version{Version: "1.0"}, + } + err := mval.Valid() + assert.NoError(t, err) + }) +} From 26a378966bd79d6d75e154d51bf98ffba7efccb3 Mon Sep 17 00:00:00 2001 From: Ravjot Singh Date: Fri, 3 Jan 2025 10:07:19 +0530 Subject: [PATCH 062/110] Docs: Improved Extension Points, Their Parent Structures, and Where You Call RegisterExtensions() Signed-off-by: Ravjot Singh --- extensions/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/README.md b/extensions/README.md index 10a4e823..3c8d0153 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -50,16 +50,16 @@ effectively defining new fields for the corresponding structures. In the code base, these can be identified by the embedded `Extensions` struct. Each extensible type has a corresponding `extensions.Point`. These are: -| type | extension point | -| --------------------- | ------------------------------------------------------------- | -| `comid.Comid` | `comid.ExtComid` | -| `comid.Entity` | `comid.ExtEntity` | -| `comid.FlagsMap` | `comid.ExtReferenceValueFlags`, `comid.ExtEndorsedValueFlags` | -| `comid.Mval` | `comid.ExtReferenceValue`, `comid.ExtEndorsedValue` | -| `comid.Triples` | `comid.ExtTriples` | -| `corim.Entity` | `corim.ExtEntity` | -| `corim.Signer` | `corim.ExtSigner` | -| `corim.UnsignedCorim` | `corim.ExtUnsignedCorim` | +| Extension Type | Extension Point(s) | Parent Structure | Where to Call RegisterExtensions() | +|:-------------------:|:-------------------------------------------------------------------------:|:----------------------------------------------------:|:-------------------------------------------------------------------------:| +| comid.Comid | comid.ExtComid | comid.Comid (the top-level CoMID) | On a comid.Comid instance (e.g. myComid.RegisterExtensions(extMap)) | +| comid.Entity | comid.ExtEntity | comid.Entity | Usually indirect via myComid.RegisterExtensions(...) (the Comid sees it). | +| comid.Triples | comid.ExtTriples | comid.Triples | Typically indirect via myComid.RegisterExtensions(...). | +| comid.Mval | comid.ExtReferenceValue, comid.ExtEndorsedValue, comid.ExtMval | comid.Mval (measurement-value in reference/endorsed) | Usually indirect via myComid.RegisterExtensions(...). | +| comid.FlagsMap | comid.ExtReferenceValueFlags, comid.ExtEndorsedValueFlags, comid.ExtFlags | comid.FlagsMap | Typically indirect via myComid.RegisterExtensions(...). | +| corim.UnsignedCorim | corim.ExtUnsignedCorim | corim.UnsignedCorim (the top-level CoRIM) | On a corim.UnsignedCorim instance (e.g. myCorim.RegisterExtensions(...)) | +| corim.Entity | corim.ExtEntity | corim.Entity | Usually indirect via myCorim.RegisterExtensions(...). | +| corim.Signer | corim.ExtSigner | corim.Signer | Usually indirect via myCorim.RegisterExtensions(...). | Note that `comid.Mval` and `comid.FlagsMap` are used for both reference values and endorsed values, which may be extended separately. This is why there are From f5d05a0ff6481dfd29d22f893327fc73ab189df3 Mon Sep 17 00:00:00 2001 From: Ravjot Singh Date: Sat, 11 Jan 2025 15:53:53 +0530 Subject: [PATCH 063/110] minor changes Signed-off-by: Ravjot Singh --- extensions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/README.md b/extensions/README.md index 3c8d0153..80877252 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -50,7 +50,7 @@ effectively defining new fields for the corresponding structures. In the code base, these can be identified by the embedded `Extensions` struct. Each extensible type has a corresponding `extensions.Point`. These are: -| Extension Type | Extension Point(s) | Parent Structure | Where to Call RegisterExtensions() | +| Extended Type | Extension Point(s) | Parent Structure | Where to Call RegisterExtensions() | |:-------------------:|:-------------------------------------------------------------------------:|:----------------------------------------------------:|:-------------------------------------------------------------------------:| | comid.Comid | comid.ExtComid | comid.Comid (the top-level CoMID) | On a comid.Comid instance (e.g. myComid.RegisterExtensions(extMap)) | | comid.Entity | comid.ExtEntity | comid.Entity | Usually indirect via myComid.RegisterExtensions(...) (the Comid sees it). | From 7a541c48dc5160e3d5e38a598ca56a379a02b7e3 Mon Sep 17 00:00:00 2001 From: "Akhilesh Kr." <2827daya@gmail.com> Date: Fri, 17 Jan 2025 14:58:12 +0530 Subject: [PATCH 064/110] chore(make): merge extra-lint target into lint (#153) Add extra-lint checks to the lint target to ensure they are always run during CI, removing the need for an additional target that is often neglected. This helps prevent the accumulation of regressions in the main branch. Signed-off-by: Akhilesh Kr. Yadav Co-authored-by: Akhilesh Kr. Yadav --- .github/workflows/linters.yml | 2 - Makefile | 17 ++----- comid/classid.go | 2 + comid/comid.go | 4 ++ comid/example_cca_realm_refval_test.go | 6 +-- comid/example_cca_refval_test.go | 7 +-- comid/example_psa_refval_test.go | 7 +-- comid/example_test.go | 12 ++--- comid/flagsmap.go | 59 +++++++++-------------- comid/group.go | 2 + comid/instance.go | 2 + comid/instance_test.go | 2 +- comid/measurement.go | 5 ++ comid/test_vars.go | 2 + comid/uuid_test.go | 2 +- corim/example_profile_test.go | 5 +- corim/unsignedcorim.go | 16 +++--- corim/unsignedcorim_test.go | 12 ++--- cots/abbreviated_swid_tag.go | 18 ++++--- cots/cas_and_tas_test.go | 10 ++-- cots/cots.go | 31 ++++++------ cots/eat_cwtclaims.go | 10 ++-- cots/eat_cwtclaims_test.go | 12 ++--- cots/env_group.go | 4 +- cots/example_abbreviated_swid_tag_test.go | 18 +++---- cots/example_test.go | 12 ++--- cots/test_vars.go | 1 + 27 files changed, 147 insertions(+), 133 deletions(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index f5140a8c..c61c4f87 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -22,5 +22,3 @@ jobs: go install github.com/golang/mock/mockgen@v1.5.0 - name: Run required linters in .golangci.yml plus hard-coded ones here run: make lint - - name: Run optional linters (not required to pass) - run: make lint-extra diff --git a/Makefile b/Makefile index a22b3fd4..28ec37dd 100644 --- a/Makefile +++ b/Makefile @@ -12,16 +12,10 @@ GOPKG += github.com/veraison/corim/extensions GOLINT ?= golangci-lint -ifeq ($(MAKECMDGOALS),lint) -GOLINT_ARGS ?= run --timeout=3m -else - ifeq ($(MAKECMDGOALS),lint-extra) - GOLINT_ARGS ?= run --timeout=3m --issues-exit-code=0 -E dupl -E gocritic -E gosimple -E lll -E prealloc - endif -endif +GOLINT_ARGS ?= run --timeout=3m -E dupl -E gocritic -E gosimple -E lll -E prealloc -.PHONY: lint lint-extra -lint lint-extra: +.PHONY: lint +lint: $(GOLINT) $(GOLINT_ARGS) ifeq ($(MAKECMDGOALS),test) @@ -50,7 +44,7 @@ presubmit: @echo @echo ">>> Fix any lint error" @echo - $(MAKE) lint-extra + $(MAKE) lint .PHONY: licenses licenses: ; @./scripts/licenses.sh @@ -60,8 +54,7 @@ help: @echo "Available targets:" @echo " * test: run unit tests for $(GOPKG)" @echo " * test-cover: run unit tests and measure coverage for $(GOPKG)" - @echo " * lint: lint sources using default configuration" - @echo " * lint-extra: lint sources using default configuration and some extra checkers" + @echo " * lint: lint sources using default configuration and some extra checkers" @echo " * presubmit: check you are ready to push your local branch to remote" @echo " * help: print this menu" @echo " * licenses: check licenses of dependent packages" diff --git a/comid/classid.go b/comid/classid.go index e29d3e0c..05307f12 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -94,6 +94,8 @@ func (o *ClassID) UnmarshalCBOR(data []byte) error { // uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" // int: an integer value, e.g. 7 // bytes: a variable length opaque bytes, example {0x07, 0x12, 0x34} + +//nolint:dupl func (o *ClassID) UnmarshalJSON(data []byte) error { var tnv encoding.TypeAndValue diff --git a/comid/comid.go b/comid/comid.go index 116100f4..51ca1fe9 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -242,6 +242,7 @@ func (o *Comid) AddDevIdentityKey(val KeyTriple) *Comid { return o } +// nolint:gocritic func (o Comid) Valid() error { if err := o.TagIdentity.Valid(); err != nil { return fmt.Errorf("tag-identity validation failed: %w", err) @@ -267,6 +268,7 @@ func (o Comid) Valid() error { } // ToCBOR serializes the target Comid to CBOR +// nolint:gocritic func (o Comid) ToCBOR() ([]byte, error) { if err := o.Valid(); err != nil { return nil, err @@ -291,6 +293,7 @@ func (o *Comid) FromCBOR(data []byte) error { } // ToJSON serializes the target Comid to JSON +// nolint:gocritic func (o Comid) ToJSON() ([]byte, error) { if err := o.Valid(); err != nil { return nil, err @@ -314,6 +317,7 @@ func (o *Comid) FromJSON(data []byte) error { return encoding.PopulateStructFromJSON(data, o) } +// nolint:gocritic func (o Comid) ToJSONPretty(indent string) ([]byte, error) { if err := o.Valid(); err != nil { return nil, err diff --git a/comid/example_cca_realm_refval_test.go b/comid/example_cca_realm_refval_test.go index a8907f3a..cde264b7 100644 --- a/comid/example_cca_realm_refval_test.go +++ b/comid/example_cca_realm_refval_test.go @@ -77,12 +77,12 @@ func extractRealmRefVal(rv ValueTriple) error { } return nil } - func extractMeasurements(m Measurements) error { if len(m.Values) == 0 { return fmt.Errorf("no measurements") } - for i, meas := range m.Values { + for i := range m.Values { + meas := &m.Values[i] if err := extractMeasurement(meas); err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } @@ -91,7 +91,7 @@ func extractMeasurements(m Measurements) error { return nil } -func extractMeasurement(m Measurement) error { +func extractMeasurement(m *Measurement) error { if err := extractRealmPersonalizationValue(m.Val.RawValue); err != nil { return fmt.Errorf("extracting realm personalization value: %w", err) } diff --git a/comid/example_cca_refval_test.go b/comid/example_cca_refval_test.go index 1717fa56..11faf802 100644 --- a/comid/example_cca_refval_test.go +++ b/comid/example_cca_refval_test.go @@ -58,7 +58,8 @@ func extractCCARefVal(rv ValueTriple) error { return fmt.Errorf("extracting impl-id: %w", err) } - for i, m := range rv.Measurements.Values { + for i := range rv.Measurements.Values { + m := &rv.Measurements.Values[i] if m.Key == nil { return fmt.Errorf("missing mKey at index %d", i) } @@ -75,10 +76,10 @@ func extractCCARefVal(rv ValueTriple) error { return fmt.Errorf("extracting cca-refval-id: %w", err) } if err := extractRawValue(m.Val.RawValue); err != nil { - return fmt.Errorf("extracting raw vlue: %w", err) + return fmt.Errorf("extracting raw value: %w", err) } default: - return fmt.Errorf("unexpected Mkey type: %T", t) + return fmt.Errorf("unexpected Mkey type: %T", t) } } diff --git a/comid/example_psa_refval_test.go b/comid/example_psa_refval_test.go index 18308181..3634736e 100644 --- a/comid/example_psa_refval_test.go +++ b/comid/example_psa_refval_test.go @@ -69,15 +69,16 @@ func extractSwMeasurements(m Measurements) error { if len(m.Values) == 0 { return fmt.Errorf("no measurements") } - for i, m := range m.Values { - if err := extractSwMeasurement(m); err != nil { + for i := range m.Values { + meas := &m.Values[i] + if err := extractSwMeasurement(meas); err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } } return nil } -func extractSwMeasurement(m Measurement) error { +func extractSwMeasurement(m *Measurement) error { if err := extractPSARefValID(m.Key); err != nil { return fmt.Errorf("extracting PSA refval id: %w", err) } diff --git a/comid/example_test.go b/comid/example_test.go index e807be6e..3012f08f 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -109,8 +109,8 @@ func Example_encode() { } // Output: - //a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA() { @@ -162,8 +162,8 @@ func Example_encode_PSA() { } // Output: - //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90259a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32;q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_encode_PSA_attestation_verification() { @@ -193,8 +193,8 @@ func Example_encode_PSA_attestation_verification() { } // Output: - //a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a1038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - //{"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a1038182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} } func Example_decode_JSON() { diff --git a/comid/flagsmap.go b/comid/flagsmap.go index de19147f..5a68233f 100644 --- a/comid/flagsmap.go +++ b/comid/flagsmap.go @@ -67,6 +67,7 @@ func NewFlagsMap() *FlagsMap { return &FlagsMap{} } +// nolint:gocritic func (o FlagsMap) IsEmpty() bool { if o.IsConfigured != nil || o.IsSecure != nil || o.IsRecovery != nil || o.IsDebug != nil || o.IsReplayProtected != nil || o.IsIntegrityProtected != nil || @@ -87,58 +88,43 @@ func (o *FlagsMap) AnySet() bool { return o.Extensions.anySet() } -func (o *FlagsMap) SetTrue(flags ...Flag) { +func (o *FlagsMap) setFlag(value *bool, flags ...Flag) { for _, flag := range flags { switch flag { case FlagIsConfigured: - o.IsConfigured = &True + o.IsConfigured = value case FlagIsSecure: - o.IsSecure = &True + o.IsSecure = value case FlagIsRecovery: - o.IsRecovery = &True + o.IsRecovery = value case FlagIsDebug: - o.IsDebug = &True + o.IsDebug = value case FlagIsReplayProtected: - o.IsReplayProtected = &True + o.IsReplayProtected = value case FlagIsIntegrityProtected: - o.IsIntegrityProtected = &True + o.IsIntegrityProtected = value case FlagIsRuntimeMeasured: - o.IsRuntimeMeasured = &True + o.IsRuntimeMeasured = value case FlagIsImmutable: - o.IsImmutable = &True + o.IsImmutable = value case FlagIsTcb: - o.IsTcb = &True + o.IsTcb = value default: - o.Extensions.setTrue(flag) + if value == &True { + o.Extensions.setTrue(flag) + } else { + o.Extensions.setFalse(flag) + } } } } +func (o *FlagsMap) SetTrue(flags ...Flag) { + o.setFlag(&True, flags...) +} + func (o *FlagsMap) SetFalse(flags ...Flag) { - for _, flag := range flags { - switch flag { - case FlagIsConfigured: - o.IsConfigured = &False - case FlagIsSecure: - o.IsSecure = &False - case FlagIsRecovery: - o.IsRecovery = &False - case FlagIsDebug: - o.IsDebug = &False - case FlagIsReplayProtected: - o.IsReplayProtected = &False - case FlagIsIntegrityProtected: - o.IsIntegrityProtected = &False - case FlagIsRuntimeMeasured: - o.IsRuntimeMeasured = &False - case FlagIsImmutable: - o.IsImmutable = &False - case FlagIsTcb: - o.IsTcb = &False - default: - o.Extensions.setFalse(flag) - } - } + o.setFlag(&False, flags...) } func (o *FlagsMap) Clear(flags ...Flag) { @@ -218,6 +204,7 @@ func (o *FlagsMap) UnmarshalCBOR(data []byte) error { } // MarshalCBOR serializes to CBOR +// nolint:gocritic func (o FlagsMap) MarshalCBOR() ([]byte, error) { return encoding.SerializeStructToCBOR(em, o) } @@ -228,11 +215,13 @@ func (o *FlagsMap) UnmarshalJSON(data []byte) error { } // MarshalJSON serializes to JSON +// nolint:gocritic func (o FlagsMap) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } // Valid returns an error if the FlagsMap is invalid. +// nolint:gocritic func (o FlagsMap) Valid() error { return o.Extensions.validFlagsMap(&o) } diff --git a/comid/group.go b/comid/group.go index 84908e58..274fc277 100644 --- a/comid/group.go +++ b/comid/group.go @@ -84,6 +84,8 @@ func (o *Group) UnmarshalCBOR(data []byte) error { // "type": "bytes", // "value": "MTIzNDU2Nzg5" // } + +//nolint:dupl func (o *Group) UnmarshalJSON(data []byte) error { var tnv encoding.TypeAndValue diff --git a/comid/instance.go b/comid/instance.go index f48d88d3..16ad57b5 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -82,6 +82,8 @@ func (o *Instance) UnmarshalCBOR(data []byte) error { // ueid: base64-encoded bytes, e.g. "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" // uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" // bytes: a variable-length opaque byte string, example {0x07, 0x12, 0x34} + +//nolint:dupl func (o *Instance) UnmarshalJSON(data []byte) error { var tnv encoding.TypeAndValue diff --git a/comid/instance_test.go b/comid/instance_test.go index 64ca8856..75fc2e54 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -108,7 +108,7 @@ func Test_NewBytesInstance_OK(t *testing.T) { instance, err := NewBytesInstance(v) require.NoError(t, err) got := instance.Bytes() - assert.Equal(t, testBytes[:], got) + assert.Equal(t, testBytes, got) } } diff --git a/comid/measurement.go b/comid/measurement.go index c64909f8..e7162691 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -389,6 +389,7 @@ func (o *Mval) UnmarshalCBOR(data []byte) error { } // MarshalCBOR serializes to CBOR +// nolint:gocritic func (o Mval) MarshalCBOR() ([]byte, error) { // If extensions have been registered, the collection will exist, but // might be empty. If that is the case, set it to nil to avoid @@ -409,6 +410,7 @@ func (o *Mval) UnmarshalJSON(data []byte) error { } // MarshalJSON serializes to JSON +// nolint:gocritic func (o Mval) MarshalJSON() ([]byte, error) { // If extensions have been registered, the collection will exist, but // might be empty. If that is the case, set it to nil to avoid @@ -423,6 +425,7 @@ func (o Mval) MarshalJSON() ([]byte, error) { return encoding.SerializeStructToJSON(o) } +// nolint:gocritic func (o Mval) Valid() error { // Check if no measurement values are set if o.Ver == nil && @@ -629,6 +632,7 @@ func (o *Measurement) RegisterExtensions(exts extensions.Map) error { return o.Val.RegisterExtensions(exts) } +// nolint:gocritic func (o Measurement) GetExtensions() extensions.IMapValue { return o.Val.GetExtensions() } @@ -784,6 +788,7 @@ func (o *Measurement) SetUUID(u UUID) *Measurement { return o } +// nolint:gocritic func (o Measurement) Valid() error { if o.Key != nil && o.Key.IsSet() { if err := o.Key.Valid(); err != nil { diff --git a/comid/test_vars.go b/comid/test_vars.go index eb7a29ed..5474da06 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -16,6 +16,7 @@ import ( "github.com/veraison/swid" ) +//nolint:lll var ( TestUUIDString = "31fb5abf-023e-4992-aa4e-95f9c1503bfa" TestUUID = UUID(uuid.Must(uuid.Parse(TestUUIDString))) @@ -207,6 +208,7 @@ func b64TestImplID() string { return base64.StdEncoding.EncodeToString(implID) } +//nolint:lll var ( PSARefValJSONTemplate = `{ "lang": "en-GB", diff --git a/comid/uuid_test.go b/comid/uuid_test.go index d2dc4f1e..caca7c9e 100644 --- a/comid/uuid_test.go +++ b/comid/uuid_test.go @@ -12,7 +12,7 @@ import ( func TestUUID_JSON(t *testing.T) { val := TaggedUUID(TestUUID) - expected := fmt.Sprintf(`"%s"`, val.String()) + expected := fmt.Sprintf("%q", val.String()) out, err := val.MarshalJSON() require.NoError(t, err) diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index becff460..9473b288 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -104,7 +104,8 @@ func Example_profile_unmarshal() { Extensions.MustGetString("Address")) fmt.Printf("Measurements:\n") - for _, m := range extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values { + for i := range extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values { + m := &extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values[i] val := hex.EncodeToString((*m.Val.Digests)[0].HashValue) tsInt := m.Val.Extensions.MustGetInt64("timestamp") @@ -185,7 +186,7 @@ func Example_profile_marshal() { log.Fatalf("comid validity: %v", err) } - myCorim.AddComid(*myComid) + myCorim.AddComid(myComid) buf, err := myCorim.ToCBOR() if err != nil { diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index bcaa7ece..9f34add5 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -88,13 +88,14 @@ func (o *UnsignedCorim) SetID(v interface{}) *UnsignedCorim { } // GetID retrieves the corim-id from the unsigned-corim-map as a string +// nolint:gocritic func (o UnsignedCorim) GetID() string { return o.ID.String() } // AddComid appends the CBOR encoded (and appropriately tagged) CoMID to the // tags array of the unsigned-corim-map -func (o *UnsignedCorim) AddComid(c comid.Comid) *UnsignedCorim { +func (o *UnsignedCorim) AddComid(c *comid.Comid) *UnsignedCorim { if o != nil { if c.Valid() != nil { return nil @@ -105,7 +106,7 @@ func (o *UnsignedCorim) AddComid(c comid.Comid) *UnsignedCorim { return nil } - taggedComid := append(ComidTag, comidCBOR...) + taggedComid := append(ComidTag, comidCBOR...) //nolint:gocritic o.Tags = append(o.Tags, taggedComid) } @@ -114,7 +115,7 @@ func (o *UnsignedCorim) AddComid(c comid.Comid) *UnsignedCorim { // AddCots appends the CBOR encoded (and appropriately tagged) CoTS to the // tags array of the unsigned-corim-map -func (o *UnsignedCorim) AddCots(c cots.ConciseTaStore) *UnsignedCorim { +func (o *UnsignedCorim) AddCots(c *cots.ConciseTaStore) *UnsignedCorim { if o != nil { if c.Valid() != nil { return nil @@ -125,7 +126,7 @@ func (o *UnsignedCorim) AddCots(c cots.ConciseTaStore) *UnsignedCorim { return nil } - taggedCots := append(cots.CotsTag, cotsCBOR...) + taggedCots := append(cots.CotsTag, cotsCBOR...) //nolint:gocritic o.Tags = append(o.Tags, taggedCots) } @@ -134,7 +135,7 @@ func (o *UnsignedCorim) AddCots(c cots.ConciseTaStore) *UnsignedCorim { // AddCoswid appends the CBOR encoded (and appropriately tagged) CoSWID to the // tags array of the unsigned-corim-map -func (o *UnsignedCorim) AddCoswid(c swid.SoftwareIdentity) *UnsignedCorim { +func (o *UnsignedCorim) AddCoswid(c *swid.SoftwareIdentity) *UnsignedCorim { if o != nil { // Currently the swid package doesn't offer an interface // for validating the supplied CoSWID, so -- for now -- @@ -146,7 +147,7 @@ func (o *UnsignedCorim) AddCoswid(c swid.SoftwareIdentity) *UnsignedCorim { return nil } - taggedCoswid := append(CoswidTag, coswidCBOR...) + taggedCoswid := append(CoswidTag, coswidCBOR...) //nolint:gocritic o.Tags = append(o.Tags, taggedCoswid) } @@ -232,6 +233,7 @@ func (o *UnsignedCorim) AddEntity(name string, regID *string, roles ...Role) *Un } // Valid checks the validity (according to the spec) of the target unsigned CoRIM +// nolint:gocritic func (o UnsignedCorim) Valid() error { if o.ID == (swid.TagID{}) { return fmt.Errorf("empty id") @@ -279,6 +281,7 @@ func (o UnsignedCorim) Valid() error { } // ToCBOR serializes the target unsigned CoRIM to CBOR +// nolint:gocritic func (o UnsignedCorim) ToCBOR() ([]byte, error) { // If extensions have been registered, the collection will exist, but // might be empty. If that is the case, set it to nil to avoid @@ -299,6 +302,7 @@ func (o *UnsignedCorim) FromCBOR(data []byte) error { } // ToJSON serializes the target unsigned CoRIM to JSON +// nolint:gocritic func (o UnsignedCorim) ToJSON() ([]byte, error) { // If extensions have been registered, the collection will exist, but // might be empty. If that is the case, set it to nil to avoid diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 7e338b77..1857f003 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -56,7 +56,7 @@ func TestUnsignedCorim_AddComid_and_marshal(t *testing.T) { tv := NewUnsignedCorim().SetID("test corim id") require.NotNil(t, tv) - c := comid.Comid{} + c := &comid.Comid{} err := c.FromJSON([]byte(comid.PSARefValJSONTemplate)) require.Nil(t, err) @@ -76,7 +76,7 @@ func TestUnsignedCorim_AddCots_and_marshal(t *testing.T) { tv := NewUnsignedCorim().SetID("test corim id with CoTS") require.NotNil(t, tv) - c := cots.ConciseTaStore{} + c := &cots.ConciseTaStore{} err := c.FromJSON([]byte(cots.ConciseTaStoreTemplateSingleOrg)) require.Nil(t, err) @@ -102,7 +102,7 @@ func TestUnsignedCorim_AddCoswid_and_marshal(t *testing.T) { err := c.FromXML(data) require.Nil(t, err) - assert.NotNil(t, tv.AddCoswid(c)) + assert.NotNil(t, tv.AddCoswid(&c)) actual, err := tv.ToCBOR() assert.Nil(t, err) @@ -191,7 +191,7 @@ func TestUnsignedCorim_Valid_ok(t *testing.T) { SetID("invalid.tags.corim"). AddDependentRim("http://endorser.example/addon.corim", nil). SetProfile("https://arm.com/psa/iot/2.0.0"). - AddComid(*c). + AddComid(c). SetRimValidity(time.Now().Add(time.Hour), nil). AddEntity("ACME Ltd.", nil, RoleManifestCreator) @@ -325,7 +325,7 @@ func TestUnsignedCorim_ToJSON(t *testing.T) { SetID("invalid.tags.corim"). AddDependentRim("http://endorser.example/addon.corim", nil). SetProfile("https://arm.com/psa/iot/2.0.0"). - AddComid(*c) + AddComid(c) require.NotNil(t, tv) @@ -368,7 +368,7 @@ func TestUnsignedCorim_ToCBOR(t *testing.T) { SetID("invalid.tags.corim"). AddDependentRim("http://endorser.example/addon.corim", nil). SetProfile("https://arm.com/psa/iot/2.0.0"). - AddComid(*c) + AddComid(c) require.NotNil(t, tv) diff --git a/cots/abbreviated_swid_tag.go b/cots/abbreviated_swid_tag.go index 00eeb0c6..7cc64e03 100644 --- a/cots/abbreviated_swid_tag.go +++ b/cots/abbreviated_swid_tag.go @@ -158,6 +158,7 @@ func NewTag(tagID interface{}, softwareName, softwareVersion string) (*Abbreviat return &t, nil } +// nolint:gocritic func (t AbbreviatedSwidTag) Valid() error { if len(t.Entities) == 0 || t.Entities == nil { return fmt.Errorf("no entities present, must have at least 1 entity") @@ -166,17 +167,20 @@ func (t AbbreviatedSwidTag) Valid() error { } // ToXML serializes the receiver AbbreviatedSwidTag to SWID +// nolint:gocritic func (t AbbreviatedSwidTag) ToXML() ([]byte, error) { return xml.Marshal(t) } // ToJSON serializes the receiver AbbreviatedSwidTag to CoSWID using the JSON -// formatter +// formatter. +// nolint:gocritic func (t AbbreviatedSwidTag) ToJSON() ([]byte, error) { return json.Marshal(t) } // ToCBOR serializes the receiver AbbreviatedSwidTag to CoSWID +// nolint:gocritic func (t AbbreviatedSwidTag) ToCBOR() ([]byte, error) { return em.Marshal(t) } @@ -211,31 +215,31 @@ func (t *AbbreviatedSwidTag) setTagID(v interface{}) error { } // AddEntity adds the supplied Entity to the receiver AbbreviatedSwidTag -func (t *AbbreviatedSwidTag) AddEntity(e swid.Entity) error { - t.Entities = append(t.Entities, e) +func (t *AbbreviatedSwidTag) AddEntity(e *swid.Entity) error { + t.Entities = append(t.Entities, *e) return nil } // AddLink adds the supplied Link to the receiver AbbreviatedSwidTag -func (t *AbbreviatedSwidTag) AddLink(l swid.Link) error { +func (t *AbbreviatedSwidTag) AddLink(l *swid.Link) error { if t.Links == nil { t.Links = new(swid.Links) } - *t.Links = append(*t.Links, l) + *t.Links = append(*t.Links, *l) return nil } // AddSoftwareMeta adds the supplied SoftwareMeta to the receiver // AbbreviatedSwidTag -func (t *AbbreviatedSwidTag) AddSoftwareMeta(m swid.SoftwareMeta) error { +func (t *AbbreviatedSwidTag) AddSoftwareMeta(m *swid.SoftwareMeta) error { if t.SoftwareMetas == nil { t.SoftwareMetas = new(swid.SoftwareMetas) } - *t.SoftwareMetas = append(*t.SoftwareMetas, m) + *t.SoftwareMetas = append(*t.SoftwareMetas, *m) return nil } diff --git a/cots/cas_and_tas_test.go b/cots/cas_and_tas_test.go index 46c8ad1e..a8380df6 100644 --- a/cots/cas_and_tas_test.go +++ b/cots/cas_and_tas_test.go @@ -145,7 +145,7 @@ func TestTrustAnchor_JSON_Roundtrip(t *testing.T) { err := tv2.FromJSON(j) assert.Nil(t, err) - assert.True(t, len(tv2.Data) == len(ta) && 0 == bytes.Compare(ta, tv2.Data), "Compare TA value") + assert.True(t, len(tv2.Data) == len(ta) && bytes.Equal(ta, tv2.Data), "Compare TA value") assert.True(t, TaFormatCertificate == tv2.Format, "Compare TA format") } @@ -166,7 +166,7 @@ func TestTrustAnchor_CBOR_Roundtrip(t *testing.T) { err := tv2.FromCBOR(c) assert.Nil(t, err) - assert.True(t, len(tv2.Data) == len(ta) && 0 == bytes.Compare(ta, tv2.Data), "Compare TA value") + assert.True(t, len(tv2.Data) == len(ta) && bytes.Equal(ta, tv2.Data), "Compare TA value") assert.True(t, TaFormatCertificate == tv2.Format, "Compare TA format") } @@ -203,9 +203,9 @@ func TestTasAndCas_JSON_full_Roundtrip(t *testing.T) { err := tv2.FromJSON(j) assert.Nil(t, err) - assert.True(t, len(tv2.Tas[0].Data) == len(ta) && 0 == bytes.Compare(ta, tv2.Tas[0].Data), "Compare TA value") + assert.True(t, len(tv2.Tas[0].Data) == len(ta) && bytes.Equal(ta, tv2.Tas[0].Data), "Compare TA value") assert.True(t, TaFormatCertificate == tv2.Tas[0].Format, "Compare TA format") - assert.True(t, len(tv2.Cas[0]) == len(ca) && 0 == bytes.Compare(ca, tv2.Cas[0]), "Compare CA") + assert.True(t, len(tv2.Cas[0]) == len(ca) && bytes.Equal(ca, tv2.Cas[0]), "Compare CA") } func TestTasAndCas_CBOR_full_Roundtrip(t *testing.T) { @@ -221,7 +221,7 @@ func TestTasAndCas_CBOR_full_Roundtrip(t *testing.T) { err := tv2.FromCBOR(c) assert.Nil(t, err) - assert.True(t, len(tv2.Tas[0].Data) == len(ta) && 0 == bytes.Compare(ta, tv2.Tas[0].Data), "Compare TA value") + assert.True(t, len(tv2.Tas[0].Data) == len(ta) && bytes.Equal(ta, tv2.Tas[0].Data), "Compare TA value") assert.True(t, TaFormatCertificate == tv2.Tas[0].Format, "Compare TA format") assert.True(t, TaFormatCertificate == tv2.Tas[0].Format, "Compare TA format") } diff --git a/cots/cots.go b/cots/cots.go index 412bc184..bec67656 100644 --- a/cots/cots.go +++ b/cots/cots.go @@ -34,7 +34,7 @@ func (o *ConciseTaStore) SetTagIdentity(tagID interface{}, tagIDVersion *uint) * } o.TagIdentity = &comid.TagIdentity{} o.TagIdentity.TagID = *id - if nil != tagIDVersion { + if tagIDVersion != nil { o.TagIdentity.TagVersion = *tagIDVersion } } @@ -62,16 +62,16 @@ func (o *ConciseTaStore) AddPurpose(purpose string) *ConciseTaStore { return o } -func (o *ConciseTaStore) AddPermClaims(permclaim EatCWTClaim) *ConciseTaStore { +func (o *ConciseTaStore) AddPermClaims(permclaim *EatCWTClaim) *ConciseTaStore { if o != nil { - o.PermClaims = append(o.PermClaims, permclaim) + o.PermClaims = append(o.PermClaims, *permclaim) } return o } -func (o *ConciseTaStore) AddExclClaims(exclclaim EatCWTClaim) *ConciseTaStore { +func (o *ConciseTaStore) AddExclClaims(exclclaim *EatCWTClaim) *ConciseTaStore { if o != nil { - o.ExclClaims = append(o.ExclClaims, exclclaim) + o.ExclClaims = append(o.ExclClaims, *exclclaim) } return o } @@ -83,21 +83,23 @@ func (o *ConciseTaStore) SetKeys(keys TasAndCas) *ConciseTaStore { return o } -// ToCBOR serializes the target ConciseTaStore to CBOR +// ToCBOR serializes the target ConciseTaStore to CBOR. +// nolint:gocritic func (o ConciseTaStore) ToCBOR() ([]byte, error) { if err := o.Valid(); err != nil { return nil, err } - return em.Marshal(&o) + return em.Marshal(o) } -// FromCBOR deserializes a CBOR-encoded CoTS into the target ConciseTaStore +// FromCBOR deserializes a CBOR-encoded CoTS into the target ConciseTaStore. func (o *ConciseTaStore) FromCBOR(data []byte) error { return dm.Unmarshal(data, o) } -// Valid iterates over the range of individual entities to check for validity +// Valid iterates over the range of individual entities to check for validity. +// nolint:gocritic func (o ConciseTaStore) Valid() error { if o.Environments == nil { return fmt.Errorf("environmentGroups must be present") @@ -121,14 +123,15 @@ func (o ConciseTaStore) Valid() error { return nil } -// FromJSON deserializes a JSON-encoded CoTS into the target ConciseTaStore +// FromJSON deserializes a JSON-encoded CoTS into the target ConciseTaStore. func (o *ConciseTaStore) FromJSON(data []byte) error { return json.Unmarshal(data, o) } -// FromJSON deserializes a JSON-encoded CoTS into the target ConsiseTaStore +// FromJSON deserializes a JSON-encoded CoTS into the target ConsiseTaStore. +// nolint:gocritic func (o ConciseTaStore) ToJSON() ([]byte, error) { - return json.Marshal(&o) + return json.Marshal(o) } type ConciseTaStores []ConciseTaStore @@ -137,13 +140,13 @@ func NewConciseTaStores() *ConciseTaStores { return new(ConciseTaStores) } -func (o *ConciseTaStores) AddConciseTaStores(cts ConciseTaStore) *ConciseTaStores { +func (o *ConciseTaStores) AddConciseTaStores(cts *ConciseTaStore) *ConciseTaStores { if o != nil { if cts.Valid() != nil { return nil } - *o = append(*o, cts) + *o = append(*o, *cts) } return o } diff --git a/cots/eat_cwtclaims.go b/cots/eat_cwtclaims.go index 518193c3..8865161c 100644 --- a/cots/eat_cwtclaims.go +++ b/cots/eat_cwtclaims.go @@ -33,22 +33,24 @@ type EatCWTClaim struct { SoftwareVersionScheme *HardwareVersionType `cbor:"999,keyasint,omitempty" json:"swversion,omitempty"` } -// ToCBOR serializes the target EatCWTClaim to CBOR +// ToCBOR serializes the target EatCWTClaim to CBOR. +// nolint:gocritic func (o EatCWTClaim) ToCBOR() ([]byte, error) { return em.Marshal(o) } -// FromCBOR deserializes a CBOR-encoded data into the target EatCWTClaim +// FromCBOR deserializes a CBOR-encoded data into the target EatCWTClaim. func (o *EatCWTClaim) FromCBOR(data []byte) error { return dm.Unmarshal(data, o) } -// ToJSON serializes the target EatCWTClaim to JSON +// ToJSON serializes the target EatCWTClaim to JSON. +// nolint:gocritic func (o EatCWTClaim) ToJSON() ([]byte, error) { return json.Marshal(o) } -// FromJSON deserializes a JSON-encoded data into the target EatCWTClaim +// FromJSON deserializes a JSON-encoded data into the target EatCWTClaim. func (o *EatCWTClaim) FromJSON(data []byte) error { return json.Unmarshal(data, o) } diff --git a/cots/eat_cwtclaims_test.go b/cots/eat_cwtclaims_test.go index 6b4b6fcc..151ee356 100644 --- a/cots/eat_cwtclaims_test.go +++ b/cots/eat_cwtclaims_test.go @@ -67,7 +67,7 @@ func getNonce() eat.Nonce { return nonce } -func cborRoundTripper(t *testing.T, tv EatCWTClaim, expected []byte) { +func cborRoundTripper(t *testing.T, tv *EatCWTClaim, expected []byte) { data, err := tv.ToCBOR() t.Logf("CBOR: %x", data) @@ -79,10 +79,10 @@ func cborRoundTripper(t *testing.T, tv EatCWTClaim, expected []byte) { err = actual.FromCBOR(data) assert.Nil(t, err) - assert.Equal(t, tv, actual) + assert.Equal(t, *tv, actual) } -func jsonRoundTripper(t *testing.T, tv EatCWTClaim, expected string) { +func jsonRoundTripper(t *testing.T, tv *EatCWTClaim, expected string) { data, err := tv.ToJSON() t.Logf("JSON: '%s'", string(data)) @@ -94,7 +94,7 @@ func jsonRoundTripper(t *testing.T, tv EatCWTClaim, expected string) { err = actual.FromJSON(data) assert.Nil(t, err) - assert.Equal(t, tv, actual) + assert.Equal(t, *tv, actual) } func TestEatCWTClaim_Full_RoundtripCBOR(t *testing.T) { @@ -116,7 +116,7 @@ func TestEatCWTClaim_Full_RoundtripCBOR(t *testing.T) { 0xff, 0xff, 0xff, 0xff, 0xff, } - cborRoundTripper(t, tv, expected) + cborRoundTripper(t, &tv, expected) } func TestEatCWTClaim_Full_RoundtripJSON(t *testing.T) { @@ -143,7 +143,7 @@ func TestEatCWTClaim_Full_RoundtripJSON(t *testing.T) { "iat": 0, "cti": "////////" }` - jsonRoundTripper(t, tv, expected) + jsonRoundTripper(t, &tv, expected) } func TestEatCWTClaims_Valid_empty_list(t *testing.T) { diff --git a/cots/env_group.go b/cots/env_group.go index 795dd834..e050ba91 100644 --- a/cots/env_group.go +++ b/cots/env_group.go @@ -30,9 +30,9 @@ func (o *EnvironmentGroup) SetEnvironment(environment comid.Environment) *Enviro return o } -func (o *EnvironmentGroup) SetAbbreviatedSwidTag(swidtag AbbreviatedSwidTag) *EnvironmentGroup { +func (o *EnvironmentGroup) SetAbbreviatedSwidTag(swidtag *AbbreviatedSwidTag) *EnvironmentGroup { if o != nil { - o.SwidTag = &swidtag + o.SwidTag = swidtag } return o } diff --git a/cots/example_abbreviated_swid_tag_test.go b/cots/example_abbreviated_swid_tag_test.go index 2f623779..276652d8 100644 --- a/cots/example_abbreviated_swid_tag_test.go +++ b/cots/example_abbreviated_swid_tag_test.go @@ -17,20 +17,20 @@ func Example_links() { // make entity and add it to the tag entity, _ := swid.NewEntity("ACME Ltd", swid.RoleTagCreator, swid.RoleSoftwareCreator, swid.RoleAggregator) _ = entity.SetRegID("acme.example") - _ = tag.AddEntity(*entity) + _ = tag.AddEntity(entity) // make links and append them to tag link, _ := swid.NewLink("example.acme.roadrunner-hw-v1-0-0", *swid.NewRel("psa-rot-compound")) - _ = tag.AddLink(*link) + _ = tag.AddLink(link) link, _ = swid.NewLink("example.acme.roadrunner-sw-bl-v1-0-0", *swid.NewRel(swid.RelComponent)) - _ = tag.AddLink(*link) + _ = tag.AddLink(link) link, _ = swid.NewLink("example.acme.roadrunner-sw-prot-v1-0-0", *swid.NewRel(swid.RelComponent)) - _ = tag.AddLink(*link) + _ = tag.AddLink(link) link, _ = swid.NewLink("example.acme.roadrunner-sw-arot-v1-0-0", *swid.NewRel(swid.RelComponent)) - _ = tag.AddLink(*link) + _ = tag.AddLink(link) // encode tag to JSON data, _ := tag.ToJSON() @@ -54,14 +54,14 @@ func Example_completePrimaryTag() { entity, _ := swid.NewEntity("The ACME Corporation", swid.RoleTagCreator, swid.RoleSoftwareCreator) _ = entity.SetRegID("acme.com") - _ = tag.AddEntity(*entity) + _ = tag.AddEntity(entity) entity, _ = swid.NewEntity("Coyote Services, Inc.", swid.RoleDistributor) _ = entity.SetRegID("mycoyote.com") - _ = tag.AddEntity(*entity) + _ = tag.AddEntity(entity) link, _ := swid.NewLink("www.gnu.org/licenses/gpl.txt", *swid.NewRel("license")) - _ = tag.AddLink(*link) + _ = tag.AddLink(link) meta := swid.SoftwareMeta{ ActivationStatus: "trial", @@ -70,7 +70,7 @@ func Example_completePrimaryTag() { Edition: "coyote", Revision: "sp1", } - _ = tag.AddSoftwareMeta(meta) + _ = tag.AddSoftwareMeta(&meta) fileSize := int64(532712) fileHash, _ := hex.DecodeString("a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a") diff --git a/cots/example_test.go b/cots/example_test.go index 48add10e..7ec74ced 100644 --- a/cots/example_test.go +++ b/cots/example_test.go @@ -109,7 +109,7 @@ func Example_encode_environment_SWID_keys_cert() { AddEnvironmentGroup( *NewEnvironmentGroup(). SetAbbreviatedSwidTag( - AbbreviatedSwidTag{ + &AbbreviatedSwidTag{ Entities: swid.Entities{ makeZestyEntityWithRoles(swid.RoleSoftwareCreator), }, @@ -117,7 +117,7 @@ func Example_encode_environment_SWID_keys_cert() { ), ). AddPermClaims( - EatCWTClaim{ + &EatCWTClaim{ SoftwareNameLabel: &swname, }, ). @@ -178,7 +178,7 @@ func Example_list_of_cots_roundtrip() { // cts1 egSnob := NewEnvironmentGroup() - egSnob.SetAbbreviatedSwidTag(AbbreviatedSwidTag{}) + egSnob.SetAbbreviatedSwidTag(&AbbreviatedSwidTag{}) egSnob.SwidTag.Entities = swid.Entities{} eSnob := swid.Entity{EntityName: "Snobbish Apparel, Inc."} _ = eSnob.SetRoles(swid.RoleSoftwareCreator) @@ -192,7 +192,7 @@ func Example_list_of_cots_roundtrip() { exclName := "Legal Lawyer" exclClaims1 := EatCWTClaim{SoftwareNameLabel: &exclName} - cts1.AddExclClaims(exclClaims1) + cts1.AddExclClaims(&exclClaims1) // cts2 egShared := NewEnvironmentGroup() @@ -206,8 +206,8 @@ func Example_list_of_cots_roundtrip() { cts2.AddEnvironmentGroup(*egShared) cts := NewConciseTaStores() - cts.AddConciseTaStores(cts1) - cts.AddConciseTaStores(cts2) + cts.AddConciseTaStores(&cts1) + cts.AddConciseTaStores(&cts2) ctsCBOR, _ := cts.ToCBOR() var roundtripCBOR ConciseTaStores diff --git a/cots/test_vars.go b/cots/test_vars.go index 5081933d..cae4805f 100644 --- a/cots/test_vars.go +++ b/cots/test_vars.go @@ -3,6 +3,7 @@ package cots +//nolint:lll var ( ConciseTaStoreTemplateSingleOrg = `{ "tag-identity": { From de08a3a0529fe1ec10e1606c435062f5b0c4d5ef Mon Sep 17 00:00:00 2001 From: Priyanshu Thapliyal Date: Tue, 24 Dec 2024 16:15:57 +0530 Subject: [PATCH 065/110] refactor(profiles): rename Profile to ProfileManifest and update related methods Signed-off-by: Priyanshu Thapliyal --- corim/profiles.go | 20 ++++++++++---------- corim/profiles_test.go | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/corim/profiles.go b/corim/profiles.go index a5b6ed23..f36da81c 100644 --- a/corim/profiles.go +++ b/corim/profiles.go @@ -192,14 +192,14 @@ func GetUnsignedCorim(profileID *eat.Profile) *UnsignedCorim { // Profile associates an EAT profile ID with a set of extensions. It allows // obtaining new CoRIM and CoMID structures that had associated extensions // registered. -type Profile struct { +type ProfileManifest struct { ID *eat.Profile MapExtensions extensions.Map } // GetComid returns a pointer to a new comid.Comid that had the Profile's // extensions (if any) registered. -func (o *Profile) GetComid() *comid.Comid { +func (o *ProfileManifest) GetComid() *comid.Comid { ret := comid.NewComid() o.registerExtensions(ret, ComidMapExtensionPoints) return ret @@ -207,7 +207,7 @@ func (o *Profile) GetComid() *comid.Comid { // GetUnsignedCorim returns a pointer to a new UnsignedCorim that had the // Profile's extensions (if any) registered. -func (o *Profile) GetUnsignedCorim() *UnsignedCorim { +func (o *ProfileManifest) GetUnsignedCorim() *UnsignedCorim { ret := NewUnsignedCorim() ret.Profile = o.ID o.registerExtensions(ret, UnsignedCorimMapExtensionPoints) @@ -216,14 +216,14 @@ func (o *Profile) GetUnsignedCorim() *UnsignedCorim { // GetSignedCorim returns a pointer to a new SignedCorim that had the // Profile's extensions (if any) registered. -func (o *Profile) GetSignedCorim() *SignedCorim { +func (o *ProfileManifest) GetSignedCorim() *SignedCorim { ret := NewSignedCorim() ret.UnsignedCorim.Profile = o.ID o.registerExtensions(ret, SignedCorimMapExtensionPoints) return ret } -func (o *Profile) registerExtensions(e iextensible, points []extensions.Point) { +func (o *ProfileManifest) registerExtensions(e iextensible, points []extensions.Point) { exts := extensions.NewMap() for _, p := range points { if v, ok := o.MapExtensions[p]; ok { @@ -262,7 +262,7 @@ func RegisterProfile(id *eat.Profile, exts extensions.Map) error { } } - profilesRegister[strID] = Profile{ID: id, MapExtensions: exts} + profilesRegister[strID] = ProfileManifest{ID: id, MapExtensions: exts} return nil } @@ -291,14 +291,14 @@ func UnregisterProfile(id *eat.Profile) bool { // GetProfile returns the Profile associated with the specified ID, or an empty // profile if no Profile has been registered for the id. The second return // value indicates whether a profile for the ID has been found. -func GetProfile(id *eat.Profile) (Profile, bool) { +func GetProfile(id *eat.Profile) (ProfileManifest, bool) { if id == nil { - return Profile{}, false + return ProfileManifest{}, false } strID, err := id.Get() if err != nil { - return Profile{}, false + return ProfileManifest{}, false } prof, ok := profilesRegister[strID] @@ -309,7 +309,7 @@ type iextensible interface { RegisterExtensions(exts extensions.Map) error } -var profilesRegister = make(map[string]Profile) +var profilesRegister = make(map[string]ProfileManifest) func init() { for _, p := range SignedCorimMapExtensionPoints { diff --git a/corim/profiles_test.go b/corim/profiles_test.go index 4b4a917e..23fffac7 100644 --- a/corim/profiles_test.go +++ b/corim/profiles_test.go @@ -65,7 +65,7 @@ func TestProfile_getters(t *testing.T) { id, err := eat.NewProfile("1.2.3") require.NoError(t, err) - profile := Profile{ + profile := ProfileManifest{ ID: id, MapExtensions: extensions.NewMap(). Add(comid.ExtComid, &struct{}{}). From 834cb46d310d35370b60937b117f455b765f05c9 Mon Sep 17 00:00:00 2001 From: Priyanshu Thapliyal Date: Wed, 8 Jan 2025 23:08:47 +0530 Subject: [PATCH 066/110] refactor(profiles): rename GetProfile to GetProfileManifest for clarity Signed-off-by: Priyanshu Thapliyal --- corim/example_profile_test.go | 6 +++--- corim/profiles.go | 28 ++++++++++++++-------------- corim/profiles_test.go | 24 ++++++++++++------------ extensions/README.md | 2 +- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index 9473b288..956b3248 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -134,13 +134,13 @@ func Example_profile_marshal() { panic(err) } - profile, ok := GetProfile(profileID) + profileManifest, ok := GetProfileManifest(profileID) if !ok { log.Fatalf("profile %v not found", profileID) } - myCorim := profile.GetUnsignedCorim() - myComid := profile.GetComid(). + myCorim := profileManifest.GetUnsignedCorim() + myComid := profileManifest.GetComid(). SetLanguage("en-GB"). SetTagIdentity("example", 0). // Adding an entity to the Entities collection also registers diff --git a/corim/profiles.go b/corim/profiles.go index f36da81c..05a2682a 100644 --- a/corim/profiles.go +++ b/corim/profiles.go @@ -117,9 +117,9 @@ func UnmarshalUnsignedCorimFromJSON(buf []byte) (*UnsignedCorim, error) { func UnmarshalComidFromCBOR(buf []byte, profileID *eat.Profile) (*comid.Comid, error) { var ret *comid.Comid - profile, ok := GetProfile(profileID) + profileManifest, ok := GetProfileManifest(profileID) if ok { - ret = profile.GetComid() + ret = profileManifest.GetComid() } else { ret = comid.NewComid() } @@ -140,7 +140,7 @@ func GetSignedCorim(profileID *eat.Profile) *SignedCorim { if profileID == nil { ret = NewSignedCorim() } else { - profile, ok := GetProfile(profileID) + profileManifest, ok := GetProfileManifest(profileID) if !ok { // unknown profile -- treat here like an unprofiled // CoRIM. While the CoRIM spec states that unknown @@ -153,7 +153,7 @@ func GetSignedCorim(profileID *eat.Profile) *SignedCorim { // additional fields may not be registered. ret = NewSignedCorim() } else { - ret = profile.GetSignedCorim() + ret = profileManifest.GetSignedCorim() } } @@ -169,7 +169,7 @@ func GetUnsignedCorim(profileID *eat.Profile) *UnsignedCorim { if profileID == nil { ret = NewUnsignedCorim() } else { - profile, ok := GetProfile(profileID) + profileManifest, ok := GetProfileManifest(profileID) if !ok { // unknown profile -- treat here like an unprofiled // CoRIM. While the CoRIM spec states that unknown @@ -182,14 +182,14 @@ func GetUnsignedCorim(profileID *eat.Profile) *UnsignedCorim { // additional fields may not be registered. ret = NewUnsignedCorim() } else { - ret = profile.GetUnsignedCorim() + ret = profileManifest.GetUnsignedCorim() } } return ret } -// Profile associates an EAT profile ID with a set of extensions. It allows +// ProfileManifest associates an EAT profile ID with a set of extensions. It allows // obtaining new CoRIM and CoMID structures that had associated extensions // registered. type ProfileManifest struct { @@ -197,7 +197,7 @@ type ProfileManifest struct { MapExtensions extensions.Map } -// GetComid returns a pointer to a new comid.Comid that had the Profile's +// GetComid returns a pointer to a new comid.Comid that had the ProfileManifest's // extensions (if any) registered. func (o *ProfileManifest) GetComid() *comid.Comid { ret := comid.NewComid() @@ -206,7 +206,7 @@ func (o *ProfileManifest) GetComid() *comid.Comid { } // GetUnsignedCorim returns a pointer to a new UnsignedCorim that had the -// Profile's extensions (if any) registered. +// ProfileManifest's extensions (if any) registered. func (o *ProfileManifest) GetUnsignedCorim() *UnsignedCorim { ret := NewUnsignedCorim() ret.Profile = o.ID @@ -215,7 +215,7 @@ func (o *ProfileManifest) GetUnsignedCorim() *UnsignedCorim { } // GetSignedCorim returns a pointer to a new SignedCorim that had the -// Profile's extensions (if any) registered. +// ProfileManifest's extensions (if any) registered. func (o *ProfileManifest) GetSignedCorim() *SignedCorim { ret := NewSignedCorim() ret.UnsignedCorim.Profile = o.ID @@ -288,10 +288,10 @@ func UnregisterProfile(id *eat.Profile) bool { return false } -// GetProfile returns the Profile associated with the specified ID, or an empty -// profile if no Profile has been registered for the id. The second return -// value indicates whether a profile for the ID has been found. -func GetProfile(id *eat.Profile) (ProfileManifest, bool) { +// GetProfileManifest returns the ProfileManifest associated with the specified ID, or an empty +// profileManifest if no ProfileManifest has been registered for the id. The second return +// value indicates whether a profileManifest for the ID has been found. +func GetProfileManifest(id *eat.Profile) (ProfileManifest, bool) { if id == nil { return ProfileManifest{}, false } diff --git a/corim/profiles_test.go b/corim/profiles_test.go index 23fffac7..731cc980 100644 --- a/corim/profiles_test.go +++ b/corim/profiles_test.go @@ -12,7 +12,7 @@ import ( "github.com/veraison/eat" ) -func TestProfile_registration(t *testing.T) { +func TestProfileManifest_registration(t *testing.T) { exts := extensions.NewMap() err := RegisterProfile(&eat.Profile{}, exts) @@ -40,11 +40,11 @@ func TestProfile_registration(t *testing.T) { err = RegisterProfile(p2, exts) assert.NoError(t, err) - prof, ok := GetProfile(p1) + prof, ok := GetProfileManifest(p1) assert.True(t, ok) assert.Equal(t, exts, prof.MapExtensions) - _, ok = GetProfile(&eat.Profile{}) + _, ok = GetProfileManifest(&eat.Profile{}) assert.False(t, ok) p3, err := eat.NewProfile("2.3.4") @@ -61,11 +61,11 @@ func TestProfile_registration(t *testing.T) { UnregisterProfile(p1) } -func TestProfile_getters(t *testing.T) { +func TestProfileManifest_getters(t *testing.T) { id, err := eat.NewProfile("1.2.3") require.NoError(t, err) - profile := ProfileManifest{ + profileManifest := ProfileManifest{ ID: id, MapExtensions: extensions.NewMap(). Add(comid.ExtComid, &struct{}{}). @@ -73,18 +73,18 @@ func TestProfile_getters(t *testing.T) { Add(ExtSigner, &struct{}{}), } - c := profile.GetComid() + c := profileManifest.GetComid() assert.NotNil(t, c.Extensions.IMapValue) - u := profile.GetUnsignedCorim() + u := profileManifest.GetUnsignedCorim() assert.NotNil(t, u.Extensions.IMapValue) - s := profile.GetSignedCorim() + s := profileManifest.GetSignedCorim() assert.NotNil(t, s.UnsignedCorim.Extensions.IMapValue) assert.NotNil(t, s.Meta.Signer.Extensions.IMapValue) } -func TestProfile_marshaling(t *testing.T) { +func TestProfileManifest_marshaling(t *testing.T) { type corimExtensions struct { Extension1 *string `cbor:"-1,keyasint,omitempty" json:"ext1,omitempty"` } @@ -117,7 +117,7 @@ func TestProfile_marshaling(t *testing.T) { assert.Equal(t, profID, c.Profile) assert.Equal(t, "foo", c.Extensions.MustGetString("Extension1")) - profile, ok := GetProfile(c.Profile) + profileManifest, ok := GetProfileManifest(c.Profile) assert.True(t, ok) cmd, err := UnmarshalComidFromCBOR(c.Tags[0], c.Profile) @@ -158,11 +158,11 @@ func TestProfile_marshaling(t *testing.T) { assert.Equal(t, profID, c.Profile) assert.Equal(t, "foo", c.Extensions.MustGetString("Extension1")) - cmd = profile.GetComid() + cmd = profileManifest.GetComid() err = cmd.FromJSON(testComidJSON) assert.NoError(t, err) - cmd = profile.GetComid() + cmd = profileManifest.GetComid() err = cmd.FromJSON(testComidWithExtensionsJSON) assert.NoError(t, err) diff --git a/extensions/README.md b/extensions/README.md index 80877252..d05c9029 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -209,7 +209,7 @@ func main() { Map extensions may be grouped into profiles. A profile is registered, associating an `eat.Profile` with an `extensions.Map`. A registered profile can -be obtained by calling `corim.GetProfile()`, which returns a `corim.Profile` +be obtained by calling `corim.GetProfileManifest()`, which returns a `corim.ProfileManifest` object which can be used to obtain `corim.UnsignedCorim`, `corim.SignedCorim` and `comid.Comid` instances that have the associated extensions registered. `corim.UnmarshalUnsignedCorimFromCBOR()` will automatically look up a From 23deee405044f7a7ccf4bdab321fe9626a820afb Mon Sep 17 00:00:00 2001 From: Pranjal Kole <61913668+pranjalkole@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:26:16 +0530 Subject: [PATCH 067/110] test(comid): improve ToCBOR and FromCBOR test coverage for env-map The ToCBOR and FromCBOR have been changed to the following order: Empty Class only Instance only Group only Class and Instance Class and Group Instance and Group Class and Instance and Group The missing tests have also been added. Signed-off-by: Pranjal Kole --- comid/environment_test.go | 165 ++++++++++++++++++++++++++++++++------ 1 file changed, 142 insertions(+), 23 deletions(-) diff --git a/comid/environment_test.go b/comid/environment_test.go index 1d2abb40..f803b490 100644 --- a/comid/environment_test.go +++ b/comid/environment_test.go @@ -58,6 +58,13 @@ func TestEnvironment_Valid_ok_with_class(t *testing.T) { assert.Nil(t, err) } +func TestEnvironment_ToCBOR_empty(t *testing.T) { + var actual Environment + _, err := actual.ToCBOR() + + assert.EqualError(t, err, "environment must not be empty") +} + func TestEnvironment_ToCBOR_class_only(t *testing.T) { tv := Environment{ Class: NewClassUUID(TestUUID), @@ -75,6 +82,40 @@ func TestEnvironment_ToCBOR_class_only(t *testing.T) { assert.Equal(t, expected, actual) } +func TestEnvironment_ToCBOR_instance_only(t *testing.T) { + tv := Environment{ + Instance: MustNewUEIDInstance(TestUEID), + } + require.NotNil(t, tv.Instance) + + // {1: 550(h'02DEADBEEFDEAD')} + expected := MustHexDecode(t, "a101d902264702deadbeefdead") + + actual, err := tv.ToCBOR() + + fmt.Printf("CBOR: %x\n", actual) + + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +func TestEnvironment_ToCBOR_group_only(t *testing.T) { + tv := Environment{ + Group: MustNewUUIDGroup(TestUUID), + } + require.NotNil(t, tv.Group) + + // {2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} + expected := MustHexDecode(t, "a102d8255031fb5abf023e4992aa4e95f9c1503bfa") + + actual, err := tv.ToCBOR() + + fmt.Printf("CBOR: %x\n", actual) + + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + func TestEnvironment_ToCBOR_class_and_instance(t *testing.T) { tv := Environment{ Class: NewClassUUID(TestUUID), @@ -94,14 +135,16 @@ func TestEnvironment_ToCBOR_class_and_instance(t *testing.T) { assert.Equal(t, expected, actual) } -func TestEnvironment_ToCBOR_instance_only(t *testing.T) { +func TestEnvironment_ToCBOR_class_and_group(t *testing.T) { tv := Environment{ - Instance: MustNewUEIDInstance(TestUEID), + Class: NewClassUUID(TestUUID), + Group: MustNewUUIDGroup(TestUUID), } - require.NotNil(t, tv.Instance) + require.NotNil(t, tv.Class) + require.NotNil(t, tv.Group) - // {1: 550(h'02DEADBEEFDEAD')} - expected := MustHexDecode(t, "a101d902264702deadbeefdead") + // {0: {0: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')}, 2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} + expected := MustHexDecode(t, "a200a100d8255031fb5abf023e4992aa4e95f9c1503bfa02d8255031fb5abf023e4992aa4e95f9c1503bfa") actual, err := tv.ToCBOR() @@ -111,14 +154,16 @@ func TestEnvironment_ToCBOR_instance_only(t *testing.T) { assert.Equal(t, expected, actual) } -func TestEnvironment_ToCBOR_group_only(t *testing.T) { +func TestEnvironment_ToCBOR_instance_and_group(t *testing.T) { tv := Environment{ - Group: MustNewUUIDGroup(TestUUID), + Instance: MustNewUEIDInstance(TestUEID), + Group: MustNewUUIDGroup(TestUUID), } + require.NotNil(t, tv.Instance) require.NotNil(t, tv.Group) - // {2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} - expected := MustHexDecode(t, "a102d8255031fb5abf023e4992aa4e95f9c1503bfa") + // {1: 550(h'02DEADBEEFDEAD'), 2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} + expected := MustHexDecode(t, "a201d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa") actual, err := tv.ToCBOR() @@ -128,20 +173,25 @@ func TestEnvironment_ToCBOR_group_only(t *testing.T) { assert.Equal(t, expected, actual) } -func TestEnvironment_FromCBOR_empty(t *testing.T) { - tv := MustHexDecode(t, "a0") +func TestEnvironment_ToCBOR_class_and_instance_and_group(t *testing.T) { + tv := Environment{ + Class: NewClassUUID(TestUUID), + Instance: MustNewUEIDInstance(TestUEID), + Group: MustNewUUIDGroup(TestUUID), + } + require.NotNil(t, tv.Class) + require.NotNil(t, tv.Instance) + require.NotNil(t, tv.Group) - var actual Environment - err := actual.FromCBOR(tv) + // {0: {0: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')}, 1: 550(h'02DEADBEEFDEAD'), 2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} + expected := MustHexDecode(t, "a300a100d8255031fb5abf023e4992aa4e95f9c1503bfa01d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa") - assert.EqualError(t, err, "environment must not be empty") -} + actual, err := tv.ToCBOR() -func TestEnvironment_ToCBOR_empty(t *testing.T) { - var actual Environment - _, err := actual.ToCBOR() + fmt.Printf("CBOR: %x\n", actual) - assert.EqualError(t, err, "environment must not be empty") + assert.Nil(t, err) + assert.Equal(t, expected, actual) } func TestEnvironment_FromCBOR_unknown_map_entry(t *testing.T) { @@ -155,6 +205,15 @@ func TestEnvironment_FromCBOR_unknown_map_entry(t *testing.T) { assert.EqualError(t, err, "environment must not be empty") } +func TestEnvironment_FromCBOR_empty(t *testing.T) { + tv := MustHexDecode(t, "a0") + + var actual Environment + err := actual.FromCBOR(tv) + + assert.EqualError(t, err, "environment must not be empty") +} + func TestEnvironment_FromCBOR_class_only(t *testing.T) { // {0: {0: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')}} tv := MustHexDecode(t, "a100a100d8255031fb5abf023e4992aa4e95f9c1503bfa") @@ -169,6 +228,34 @@ func TestEnvironment_FromCBOR_class_only(t *testing.T) { assert.Nil(t, actual.Group) } +func TestEnvironment_FromCBOR_instance_only(t *testing.T) { + // {1: 550(h'02DEADBEEFDEAD')} + tv := MustHexDecode(t, "a101d902264702deadbeefdead") + + var actual Environment + err := actual.FromCBOR(tv) + + assert.Nil(t, err) + assert.Nil(t, actual.Class) + assert.NotNil(t, actual.Instance) + assert.Equal(t, []byte(TestUEID), actual.Instance.Bytes()) + assert.Nil(t, actual.Group) +} + +func TestEnvironment_FromCBOR_group_only(t *testing.T) { + // {2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} + tv := MustHexDecode(t, "a102d8255031fb5abf023e4992aa4e95f9c1503bfa") + + var actual Environment + err := actual.FromCBOR(tv) + + assert.Nil(t, err) + assert.Nil(t, actual.Class) + assert.Nil(t, actual.Instance) + assert.NotNil(t, actual.Group) + assert.Equal(t, TestUUIDString, actual.Group.String()) +} + func TestEnvironment_FromCBOR_class_and_instance(t *testing.T) { // {0: {0: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')}, 1: 550(h'02DEADBEEFDEAD')} tv := MustHexDecode(t, "a200a100d8255031fb5abf023e4992aa4e95f9c1503bfa01d902264702deadbeefdead") @@ -184,20 +271,52 @@ func TestEnvironment_FromCBOR_class_and_instance(t *testing.T) { assert.Nil(t, actual.Group) } -func TestEnvironment_FromCBOR_group_only(t *testing.T) { - // {2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} - tv := MustHexDecode(t, "a102d8255031fb5abf023e4992aa4e95f9c1503bfa") +func TestEnvironment_FromCBOR_class_and_group(t *testing.T) { + // {0: {0: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')}, 2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} + tv := MustHexDecode(t, "a200a100d8255031fb5abf023e4992aa4e95f9c1503bfa02d8255031fb5abf023e4992aa4e95f9c1503bfa") var actual Environment err := actual.FromCBOR(tv) assert.Nil(t, err) - assert.Nil(t, actual.Class) + assert.NotNil(t, actual.Class) + assert.Equal(t, TestUUIDString, actual.Class.ClassID.String()) assert.Nil(t, actual.Instance) assert.NotNil(t, actual.Group) assert.Equal(t, TestUUIDString, actual.Group.String()) } +func TestEnvironment_FromCBOR_instance_and_group(t *testing.T) { + // {1: 550(h'02DEADBEEFDEAD'), 2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} + tv := MustHexDecode(t, "a201d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa") + + var actual Environment + err := actual.FromCBOR(tv) + + assert.Nil(t, err) + assert.Nil(t, actual.Class) + assert.NotNil(t, actual.Instance) + assert.Equal(t, []byte(TestUEID), actual.Instance.Bytes()) + assert.NotNil(t, actual.Group) + assert.Equal(t, TestUUIDString, actual.Group.String()) +} + +func TestEnvironment_FromCBOR_class_and_instance_and_group(t *testing.T) { + // {0: {0: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')}, 1: 550(h'02DEADBEEFDEAD'), 2: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA')} + tv := MustHexDecode(t, "a300a100d8255031fb5abf023e4992aa4e95f9c1503bfa01d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa") + + var actual Environment + err := actual.FromCBOR(tv) + + assert.Nil(t, err) + assert.NotNil(t, actual.Class) + assert.Equal(t, TestUUIDString, actual.Class.ClassID.String()) + assert.NotNil(t, actual.Instance) + assert.Equal(t, []byte(TestUEID), actual.Instance.Bytes()) + assert.NotNil(t, actual.Group) + assert.Equal(t, TestUUIDString, actual.Group.String()) +} + func TestEnviroment_JSON(t *testing.T) { testEnv := Environment{ Class: NewClassUUID(TestUUID), From 14a8274c261a585e109785227f5286beecf239cf Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Mon, 20 Jan 2025 10:22:20 +0100 Subject: [PATCH 068/110] chore: run `go fmt` over 23deee4 (#158) Signed-off-by: Thomas Fossati --- comid/environment_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/comid/environment_test.go b/comid/environment_test.go index f803b490..c179da55 100644 --- a/comid/environment_test.go +++ b/comid/environment_test.go @@ -137,8 +137,8 @@ func TestEnvironment_ToCBOR_class_and_instance(t *testing.T) { func TestEnvironment_ToCBOR_class_and_group(t *testing.T) { tv := Environment{ - Class: NewClassUUID(TestUUID), - Group: MustNewUUIDGroup(TestUUID), + Class: NewClassUUID(TestUUID), + Group: MustNewUUIDGroup(TestUUID), } require.NotNil(t, tv.Class) require.NotNil(t, tv.Group) From f0fb974f22a110d9d919514823d9dc73249f7f6a Mon Sep 17 00:00:00 2001 From: "Akhilesh Kr. Yadav" Date: Sat, 8 Feb 2025 18:09:44 +0530 Subject: [PATCH 069/110] feat: adds 3-level certificate chain generation script Signed-off-by: Akhilesh Kr. Yadav Addressing comments on #165 Signed-off-by: Akhilesh Kr. Yadav Addressing comments on #165 Signed-off-by: Akhilesh Kr. Yadav Addressing comments on #165 Signed-off-by: Akhilesh Kr. Yadav Addressing comments on #165 Signed-off-by: Akhilesh Kr. Yadav Addressing comments on #165 Signed-off-by: Akhilesh Kr. Yadav --- Makefile | 6 ++ misc/endEntity.der | Bin 0 -> 384 bytes misc/endEntity.key | 5 + misc/intermediateCA.der | Bin 0 -> 369 bytes misc/rootCA.der | Bin 0 -> 386 bytes scripts/gen-certs.sh | 202 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+) create mode 100644 misc/endEntity.der create mode 100644 misc/endEntity.key create mode 100644 misc/intermediateCA.der create mode 100644 misc/rootCA.der create mode 100644 scripts/gen-certs.sh diff --git a/Makefile b/Makefile index 28ec37dd..4e52f6f2 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,11 @@ presubmit: .PHONY: licenses licenses: ; @./scripts/licenses.sh +.PHONY: test-certs +test-certs: + @echo "Regenerating certificate chain..." + @$(SHELL) scripts/gen-certs.sh create + .PHONY: help help: @echo "Available targets:" @@ -58,3 +63,4 @@ help: @echo " * presubmit: check you are ready to push your local branch to remote" @echo " * help: print this menu" @echo " * licenses: check licenses of dependent packages" + @echo " * test-certs: regenerate the certificate chain" diff --git a/misc/endEntity.der b/misc/endEntity.der new file mode 100644 index 0000000000000000000000000000000000000000..0d1c6ce6c6e16ca42ffdc5b0da10fbc843b9c55f GIT binary patch literal 384 zcmXqLVyrP}VpLkd%*4pVB*I^MvQ=5`*!{Oh?q8DUnjEFoxz@*ki;Y98&EuRc3p0~} zn4yS)5F2wS3o{S5V{&e)f_r9FZho`YIG>rst&=8AKWgv$2D1Vq%0^$;`;k?8Lyrd0z4KwhIbjo@`gP zE@j;->~#70@{D*tQTCr<@g-YoCNwd!-8pzue~XZsA5ZP8q#x;OL2CWl+DcVHOe_I~ z?6!-Y3>*w(f!53Nv52vVto^%@ZEbkB%+xyX{*2AZclU=bl`)V9Nh`BR7>G599DM!G zx;rU;)x}p)e?N)^UkXZ3Fh&jrW)B7fS0)8vl{e<5b_eI>O`IF`pVQ<2J^k7lSB;M} yW(A}O&yLNV!KBFW>s@oh_pa#yS?agXOuM0(m;Af?%BnU~L>dUQv4hQKVuV`4%*f8{#K6KT`ERfI_SUEMTnEkv{0LS*>MYcM zkXKKo^OMb{2`=9@vUD91K5cS-hh~3WQAAwW&vX63@gDAu`{{$(;9wvN zv{IIjMT|w{;OlqR-AVDQF20KT`%x_TQc!w=v4K2DTA4+{K&(NeeXoxCx7NLuUwi+` z9g|$}?y~-tzsMeEc4shfVN$60?~|ujuf<xOL`b~?24FU~hfzFiWV-aH!Y2T}({;hSd z<=5W7a>pbWyt}Nw<*$J}NLrai!a%G6y8?cY0%1nR|17Kq%s>h`9GE>B4BVI$8QyXU za%M8Tc)i$JNb6hOGM~p*Z?ZU<^vYlTnUsI(SKD!*lI}R)j&8f(D<-|)C;0G9f6(6R Tv*#qZ&oGsY_Xx^xJs%7Juu6k0 literal 0 HcmV?d00001 diff --git a/scripts/gen-certs.sh b/scripts/gen-certs.sh new file mode 100644 index 00000000..5b0d6c4b --- /dev/null +++ b/scripts/gen-certs.sh @@ -0,0 +1,202 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +set -eu +set -o pipefail + +ROOT_CERT_NAME=rootCA +INTERMEDIATE_CERT_NAME=intermediateCA +END_ENTITY_CERT_NAME=endEntity + +THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +MISC_DIR="$THIS_DIR/../misc" + +mkdir -p "$MISC_DIR" + +trap '[[ $_should_clean_certs_artifacts == true ]] && clean_certs_artifacts' EXIT + +function create_root_cert() { + _check_openssl + + if [[ -f "${MISC_DIR}/${ROOT_CERT_NAME}.der" ]]; then + echo "Root certificate already exists. Skipping creation." + return + fi + + openssl ecparam -name prime256v1 -genkey -noout -out ${MISC_DIR}/${ROOT_CERT_NAME}.key + openssl req -x509 -new -nodes -key ${MISC_DIR}/${ROOT_CERT_NAME}.key \ + -sha256 -days 3650 \ + -subj "/CN=Acme Inc." \ + -out ${MISC_DIR}/${ROOT_CERT_NAME}.crt + openssl x509 -in ${MISC_DIR}/${ROOT_CERT_NAME}.crt -outform der \ + -out ${MISC_DIR}/${ROOT_CERT_NAME}.der + rm -f ${MISC_DIR}/${ROOT_CERT_NAME}.crt + + echo "Created ${MISC_DIR}/${ROOT_CERT_NAME}.der and ${MISC_DIR}/${ROOT_CERT_NAME}.key" +} + +function create_intermediate_cert() { + _check_openssl + _check_root_cert + + if [[ -f "${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.der" ]]; then + echo "Intermediate certificate already exists. Skipping creation." + return + fi + + openssl ecparam -name prime256v1 -genkey -noout -out ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.key + openssl req -new -key ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.key \ + -out ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.csr \ + -subj "/CN=Acme Gizmos" + openssl x509 -req -in ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.csr \ + -CA ${MISC_DIR}/${ROOT_CERT_NAME}.der \ + -CAkey ${MISC_DIR}/${ROOT_CERT_NAME}.key \ + -CAcreateserial \ + -out ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.crt \ + -days 3650 -sha256 + openssl x509 -in ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.crt \ + -outform der -out ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.der + rm -f ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.crt + + echo "Created ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.der and ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.key" +} + +function create_end_entity_cert() { + _check_openssl + _check_root_cert + + if ([[ -f "${MISC_DIR}/${END_ENTITY_CERT_NAME}.der" ]] && [[ -f "${MISC_DIR}/${END_ENTITY_CERT_NAME}.key" ]]); then + echo "End-entity certificate and key already exist. Skipping creation." + return + fi + + openssl ecparam -name prime256v1 -genkey -noout -out ${MISC_DIR}/${END_ENTITY_CERT_NAME}.key + openssl req -new -key ${MISC_DIR}/${END_ENTITY_CERT_NAME}.key \ + -out ${MISC_DIR}/${END_ENTITY_CERT_NAME}.csr \ + -subj "/CN=Acme Gizmo CoRIM signer" + openssl x509 -req -in ${MISC_DIR}/${END_ENTITY_CERT_NAME}.csr \ + -CA ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.der \ + -CAkey ${MISC_DIR}/${INTERMEDIATE_CERT_NAME}.key \ + -CAcreateserial \ + -out ${MISC_DIR}/${END_ENTITY_CERT_NAME}.crt \ + -days 1825 -sha256 \ + -CAform der + openssl x509 -in ${MISC_DIR}/${END_ENTITY_CERT_NAME}.crt \ + -outform der -out ${MISC_DIR}/${END_ENTITY_CERT_NAME}.der + rm -f ${MISC_DIR}/${END_ENTITY_CERT_NAME}.crt + + echo "Created ${MISC_DIR}/${END_ENTITY_CERT_NAME}.der and ${MISC_DIR}/${END_ENTITY_CERT_NAME}.key" +} + +function clean_certs_artifacts() { + pushd "$MISC_DIR" > /dev/null || exit 1 + echo "rm -f -- *.csr *.srl" + rm -f -- *.csr *.srl + popd > /dev/null || exit 1 +} + +function clean_cert() { + pushd "$MISC_DIR" > /dev/null || exit 1 + local cert="$1" + echo "rm -f \"${cert}.der\" \"${cert}.key\"" + rm -f "${cert}.der" "${cert}.key" + popd > /dev/null || exit 1 +} + +function clean_all() { + clean_certs_artifacts + clean_cert "$ROOT_CERT_NAME" + clean_cert "$INTERMEDIATE_CERT_NAME" + clean_cert "$END_ENTITY_CERT_NAME" +} + +function help() { + set +e + read -r -d '' usage <<-EOF + Usage: gen-certs [-h] [-C] [COMMAND] + + This script is used to (re-)generate certificates used for a veraison + deployment. The certificates are signed by a CA certificate called + ${ROOT_CERT_NAME}.crt. If this does not exist, a self-signed one will + be (re-)generated. + + Commands: + + create + Create the root, intermediate, and end-entity certificates. + + clean_certs_artifacts + Clean output artifacts for the certificates. + + clean_all + Clean both intermediate and output artifacts for everything (including + the root CA cert). + + help + Print this message and exit (same as -h option). + + Options: + + -h Print this message and exit. + -C Do not clean up intermediate artifacts (e.g., CSRs). + + Note: Regenerating the certificate chain is an exceptional action and should + only be done when necessary (e.g., when certificates expire). + +EOF + + echo "$usage" + set -e +} + +function _check_openssl() { + if [[ "$(which openssl 2>/dev/null)" == "" ]]; then + echo -e "ERROR: openssl executable must be installed to use this command." + exit 1 + fi +} + +function _check_root_cert() { + if [[ ! -f "${MISC_DIR}/${ROOT_CERT_NAME}.der" ]]; then + create_root_cert + fi +} + +_should_clean_certs_artifacts=true + +OPTIND=1 + +while getopts "hC" opt; do + case "$opt" in + h) help; exit 0;; + C) _should_clean_certs_artifacts=false;; + *) break;; + esac +done + +shift $((OPTIND-1)) +[ "${1:-}" = "--" ] && shift + +command=$1 +case $command in + help) + help + exit 0 + ;; + clean) + clean_certs_artifacts + ;; + clean_all) + clean_all + ;; + create) + create_root_cert + create_intermediate_cert + create_end_entity_cert + if [[ $_should_clean_certs_artifacts == true ]]; then + clean_certs_artifacts + fi + ;; + *) + echo -e "ERROR: unexpected command: \"$command\" (use -h for help)" + ;; +esac \ No newline at end of file From e50913b4fbc8778cb1772f4b0187685c5dc090c1 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Fri, 20 Sep 2024 12:13:50 +0100 Subject: [PATCH 070/110] feat: implement first phase of TDX Measurement Extensions - Define TDX Extension code points in the Comid Measurement Value Extension Slot - Provide Examples of TDX Platform(SEAM), Provisioning Certification Enclave(PCE) and TDX Quoting Enclace(QE) Reference Values - Implement CBOR and JSON Encoding and Decoding of TDX Extension Points to generate TDX CoMIDs The implementation adheres to https://datatracker.ietf.org/doc/draft-cds-rats-intel-corim-profile/ Signed-off-by: Yogesh Deshpande --- comid/digests_test.go | 2 - comid/tdx-profile/common.go | 9 + comid/tdx-profile/example_pce_refval_test.go | 339 ++++++++++++ comid/tdx-profile/example_qe_refval_test.go | 307 +++++++++++ comid/tdx-profile/example_seam_refval_test.go | 516 ++++++++++++++++++ comid/tdx-profile/mval_extensions.go | 60 ++ comid/tdx-profile/teeadvisoryids.go | 61 +++ comid/tdx-profile/teeadvisoryids_test.go | 78 +++ comid/tdx-profile/teeattributes.go | 27 + comid/tdx-profile/teeattributes_test.go | 35 ++ comid/tdx-profile/teeinstanceid.go | 179 ++++++ comid/tdx-profile/teeinstanceid_test.go | 118 ++++ comid/tdx-profile/teeisvproid.go | 179 ++++++ comid/tdx-profile/teeisvproid_test.go | 121 ++++ comid/tdx-profile/teemiscselect.go | 27 + comid/tdx-profile/teemiscselect_test.go | 35 ++ comid/tdx-profile/teepceid.go | 26 + comid/tdx-profile/teepceid_test.go | 36 ++ comid/tdx-profile/teetcbcompsvn.go | 36 ++ comid/tdx-profile/teetcbstatus.go | 54 ++ comid/tdx-profile/teetcbstatus_test.go | 80 +++ comid/tdx-profile/test_vars.go | 190 +++++++ .../testcases/comid_pce_refval.cbor | Bin 0 -> 382 bytes .../testcases/comid_qe_refval.cbor | Bin 0 -> 432 bytes .../testcases/comid_seam_refval.cbor | Bin 0 -> 456 bytes comid/tdx-profile/testcases/regen-from-src.sh | 21 + .../testcases/src/comid_pce_refval.diag | 34 ++ .../testcases/src/comid_qe_refval.diag | 44 ++ .../testcases/src/comid_seam_refval.diag | 51 ++ comid/tdx-profile/types.go | 14 + comid/test_vars.go | 1 + comid/triples.go | 2 +- 32 files changed, 2679 insertions(+), 3 deletions(-) create mode 100644 comid/tdx-profile/common.go create mode 100644 comid/tdx-profile/example_pce_refval_test.go create mode 100644 comid/tdx-profile/example_qe_refval_test.go create mode 100644 comid/tdx-profile/example_seam_refval_test.go create mode 100644 comid/tdx-profile/mval_extensions.go create mode 100644 comid/tdx-profile/teeadvisoryids.go create mode 100644 comid/tdx-profile/teeadvisoryids_test.go create mode 100644 comid/tdx-profile/teeattributes.go create mode 100644 comid/tdx-profile/teeattributes_test.go create mode 100644 comid/tdx-profile/teeinstanceid.go create mode 100644 comid/tdx-profile/teeinstanceid_test.go create mode 100644 comid/tdx-profile/teeisvproid.go create mode 100644 comid/tdx-profile/teeisvproid_test.go create mode 100644 comid/tdx-profile/teemiscselect.go create mode 100644 comid/tdx-profile/teemiscselect_test.go create mode 100644 comid/tdx-profile/teepceid.go create mode 100644 comid/tdx-profile/teepceid_test.go create mode 100644 comid/tdx-profile/teetcbcompsvn.go create mode 100644 comid/tdx-profile/teetcbstatus.go create mode 100644 comid/tdx-profile/teetcbstatus_test.go create mode 100644 comid/tdx-profile/test_vars.go create mode 100644 comid/tdx-profile/testcases/comid_pce_refval.cbor create mode 100644 comid/tdx-profile/testcases/comid_qe_refval.cbor create mode 100644 comid/tdx-profile/testcases/comid_seam_refval.cbor create mode 100755 comid/tdx-profile/testcases/regen-from-src.sh create mode 100644 comid/tdx-profile/testcases/src/comid_pce_refval.diag create mode 100644 comid/tdx-profile/testcases/src/comid_qe_refval.diag create mode 100644 comid/tdx-profile/testcases/src/comid_seam_refval.diag create mode 100644 comid/tdx-profile/types.go diff --git a/comid/digests_test.go b/comid/digests_test.go index ff14b359..663a064d 100644 --- a/comid/digests_test.go +++ b/comid/digests_test.go @@ -76,7 +76,6 @@ func TestDigests_AddDigest_OK(t *testing.T) { assert.Nil(t, d.Valid()) } } - func TestDigests_Valid_empty(t *testing.T) { d := NewDigests() require.NotNil(t, d) @@ -89,7 +88,6 @@ func TestDigests_Valid_empty(t *testing.T) { assert.EqualError(t, d.Valid(), "digest at index 0: unknown hash algorithm 666") } - func TestDigests_AddDigest_unknown_algo(t *testing.T) { d := NewDigests() require.NotNil(t, d) diff --git a/comid/tdx-profile/common.go b/comid/tdx-profile/common.go new file mode 100644 index 00000000..032915ac --- /dev/null +++ b/comid/tdx-profile/common.go @@ -0,0 +1,9 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +func isType[T any](v any) bool { + _, ok := v.(T) + return ok +} diff --git a/comid/tdx-profile/example_pce_refval_test.go b/comid/tdx-profile/example_pce_refval_test.go new file mode 100644 index 00000000..36568a38 --- /dev/null +++ b/comid/tdx-profile/example_pce_refval_test.go @@ -0,0 +1,339 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + _ "embed" + "fmt" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" + "github.com/veraison/eat" +) + +// Example_decode_PCE_JSON decodes the TDX Provisioning Certification Enclave Measurement Extensions from the given JSON Template +func Example_decode_PCE_JSON() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + manifest, found := corim.GetProfileManifest(profileID) + if !found { + fmt.Printf("CoRIM Profile NOT FOUND") + return + } + + m := manifest.GetComid() + if err := m.FromJSON([]byte(TDXPCERefValTemplate)); err != nil { + panic(err) + } + + if err := m.Valid(); err != nil { + panic(err) + } + + if err := extractPCERefVals(m); err != nil { + panic(err) + } + + // output: + // OID: 2.16.840.1.113741.1.2.3.4.6 + // Vendor: Intel Corporation + // Model: 0123456789ABCDEF + // InstanceID: 11 + // pceID: 0000 + // SVN[0]: 10 + // SVN[1]: 10 + // SVN[2]: 2 + // SVN[3]: 2 + // SVN[4]: 2 + // SVN[5]: 1 + // SVN[6]: 4 + // SVN[7]: 0 + // SVN[8]: 0 + // SVN[9]: 0 + // SVN[10]: 0 + // SVN[11]: 0 + // SVN[12]: 0 + // SVN[13]: 0 + // SVN[14]: 0 + // SVN[15]: 0 + // CryptoKey Type: pkix-base64-key + // CryptoKey Value: -----BEGIN PUBLIC KEY----- + // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== + // -----END PUBLIC KEY----- +} + +func extractPCERefVals(c *comid.Comid) error { + if c.Triples.ReferenceValues == nil { + return fmt.Errorf("no reference values triples") + } + + for i, rv := range c.Triples.ReferenceValues.Values { + if err := extractPCERefVal(rv); err != nil { + return fmt.Errorf("bad PSA reference value at index %d: %w", i, err) + } + } + + return nil +} + +func extractPCERefVal(rv comid.ValueTriple) error { + class := rv.Environment.Class + + if err := extractClassElements(class); err != nil { + return fmt.Errorf("extracting class: %w", err) + } + + measurements := rv.Measurements + if err := extractPCEMeasurements(&measurements); err != nil { + return fmt.Errorf("extracting measurements: %w", err) + } + + return nil +} + +func extractPCEMeasurements(meas *comid.Measurements) error { + if len(meas.Values) == 0 { + return fmt.Errorf("no measurements") + } + for i := range meas.Values { + m := &meas.Values[0] + if err := decodePCEMValExtensions(m); err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + + if m.AuthorizedBy != nil { + err := decodeAuthorisedBy(m) + if err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + } + } + return nil +} + +func decodePCEMValExtensions(m *comid.Measurement) error { + val, err := m.Val.Extensions.Get("instanceid") + if err != nil { + return fmt.Errorf("failed to decode instanceid from measurement extensions") + } + i, ok := val.(*TeeInstanceID) + if !ok { + fmt.Printf("val was not pointer to teeInstanceID") + } + + if i.IsBytes() { + val, err = i.GetBytes() + if err != nil { + return fmt.Errorf("failed to decode teeinstanceid: %w", err) + } + fmt.Printf("\nInstanceID: %x", val) + } else if i.IsUint() { + val, err = i.GetUint() + if err != nil { + return fmt.Errorf("failed to decode teeinstanceid: %w", err) + } + fmt.Printf("\nInstanceID: %d", val) + } else { + return fmt.Errorf("teeinstanceid is neither integer or byte string") + } + + val, err = m.Val.Extensions.Get("tcbcompsvn") + if err != nil { + return fmt.Errorf("failed to decode teetcbcompsvn from measurement extensions") + } + + tD, ok := val.(*TeeTcbCompSvn) + if !ok { + fmt.Printf("val was not pointer to teetcbcompsvn") + } + if err = tD.Valid(); err != nil { + return fmt.Errorf("invalid computed SVN: %w", err) + } + val, err = m.Val.Extensions.Get("pceid") + if err != nil { + return fmt.Errorf("failed to decode tcbevalnum from measurement extensions") + } + t, ok := val.(*TeePCEID) + if !ok { + fmt.Printf("val was not pointer to TeeTcbEvalNum") + } + if err = t.Valid(); err != nil { + return fmt.Errorf("invalid PCEID: %w", err) + } + pceID := *t + fmt.Printf("\npceID: %s", pceID) + + err = extractSVN(tD) + if err != nil { + return fmt.Errorf("unable to extract TEE Digest: %w", err) + } + return nil +} + +func extractSVN(s *TeeTcbCompSvn) error { + if s == nil { + return fmt.Errorf("no TEE TCB Comp SVN") + } + + if len(*s) > 16 { + return fmt.Errorf("computed SVN cannot be greater than 16") + } + + for i, svn := range *s { + fmt.Printf("\nSVN[%d]: %d", i, svn) + } + + return nil +} + +var ( + // test cases are based on diag files here: + // https://github.com/ietf-rats-wg/draft-ietf-rats-corim/tree/main/cddl/examples + + //go:embed testcases/comid_pce_refval.cbor + testComid3 []byte +) + +func Example_decode_PCE_CBOR() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + manifest, found := corim.GetProfileManifest(profileID) + if !found { + fmt.Printf("CoRIM Profile NOT FOUND") + return + } + + m := manifest.GetComid() + + if err := m.FromCBOR(testComid3); err != nil { + panic(err) + } + if err := m.Valid(); err != nil { + panic(err) + } + + if err := extractPCERefVals(m); err != nil { + panic(err) + } + + // Output: + // OID: 2.16.840.1.113741.1.2.3.4.5 + // Vendor: Intel Corporation + // Model: TDX PCE TCB + // InstanceID: 00112233445566778899aabbccddeeff + // pceID: 0000 + // SVN[0]: 10 + // SVN[1]: 10 + // SVN[2]: 2 + // SVN[3]: 2 + // SVN[4]: 2 + // SVN[5]: 1 + // SVN[6]: 4 + // SVN[7]: 0 + // SVN[8]: 0 + // SVN[9]: 0 + // SVN[10]: 0 + // SVN[11]: 0 + // SVN[12]: 0 + // SVN[13]: 0 + // SVN[14]: 0 + // SVN[15]: 0 + // CryptoKey Type: pkix-base64-key + // CryptoKey Value: -----BEGIN PUBLIC KEY----- + // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== + // -----END PUBLIC KEY----- +} + +func Example_encode_tdx_pce_refval_with_profile() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + manifest, found := corim.GetProfileManifest(profileID) + if !found { + fmt.Printf("CoRIM Profile NOT FOUND") + return + } + + m := manifest.GetComid() + if m == nil { + panic(err) + } + m.SetTagIdentity("43BBE37F-2E61-4B33-AED3-53CFF1428B20", 0). + AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) + + refVal := &comid.ValueTriple{} + measurement := &comid.Measurement{} + refVal.Environment = comid.Environment{ + Class: comid.NewClassOID(TestOID). + SetVendor("Intel Corporation"). + SetModel("TDX PCE TCB"), + } + + refVal.Measurements.Add(measurement) + m.Triples.AddReferenceValue(*refVal) + + err = setTDXPCEMvalExtension(&m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val) + if err != nil { + fmt.Printf("unable to set extensions :%s", err.Error()) + } + + err = m.Valid() + if err != nil { + fmt.Printf("CoMID is not Valid :%s", err.Error()) + } + + cbor, err := m.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } else { + fmt.Printf("\n To CBOR Failed: %s \n", err.Error()) + } + + json, err := m.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } else { + fmt.Printf("\n To JSON Failed \n") + } + + // Output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e026b544458205043452054434281a101a3384c182d384f685043454944303031387c900102030405060708090a0b0c0d0e0f10 + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDX PCE TCB"}},"measurements":[{"value":{"instanceid":{"type":"uint","value":45},"pceid":"PCEID001","tcbcompsvn":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}}]}]}} +} + +func setTDXPCEMvalExtension(val *comid.Mval) error { + instanceID, err := NewTeeInstanceID(TestUIntInstance) + if err != nil { + return fmt.Errorf("unable to get teeinstanceID %w", err) + } + err = val.Extensions.Set("instanceid", instanceID) + if err != nil { + return fmt.Errorf("unable to set teeinstanceID %w", err) + } + + p, err := NewTeePCEID(TestPCEID) + if err != nil { + return fmt.Errorf("unable to get NewTeepceID %w", err) + } + err = val.Extensions.Set("pceid", p) + if err != nil { + return fmt.Errorf("unable to set teepceID %w", err) + } + + c, err := NewTeeTcbCompSVN(TestCompSVN) + if err != nil { + return fmt.Errorf("failed to get TeeTcbCompSVN %w", err) + } + + err = val.Extensions.Set("tcbcompsvn", c) + if err != nil { + return fmt.Errorf("unable to set teetcbcompsvn: %w", err) + } + return nil +} diff --git a/comid/tdx-profile/example_qe_refval_test.go b/comid/tdx-profile/example_qe_refval_test.go new file mode 100644 index 00000000..500ab937 --- /dev/null +++ b/comid/tdx-profile/example_qe_refval_test.go @@ -0,0 +1,307 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + _ "embed" + "fmt" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" + "github.com/veraison/swid" +) + +// Example_decode_QE_JSON decodes the TDX Quoting Enclave Measurement Extensions from the given JSON Template +func Example_decode_QE_JSON() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + manifest, found := corim.GetProfileManifest(profileID) + if !found { + fmt.Printf("CoRIM Profile NOT FOUND") + return + } + + m := manifest.GetComid() + if err := m.FromJSON([]byte(TDXQERefValTemplate)); err != nil { + panic(err) + } + + if err := m.Valid(); err != nil { + panic(err) + } + + if err := extractQERefVals(m); err != nil { + panic(err) + } + + // output: + // OID: 2.16.840.1.113741.1.2.3.4.1 + // Vendor: Intel Corporation + // Model: TDX QE TCB + // miscselect: c0000000fbff0000 + // tcbEvalNum: 11 + // IsvProdID: 0303 + // mrsigner Digest Alg: 1 + // mrsigner Digest Value: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 + // mrsigner Digest Alg: 8 + // mrsigner Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a + // CryptoKey Type: pkix-base64-key + // CryptoKey Value: -----BEGIN PUBLIC KEY----- + // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== + // -----END PUBLIC KEY----- +} + +func extractQERefVals(c *comid.Comid) error { + if c.Triples.ReferenceValues == nil { + return fmt.Errorf("no reference values triples") + } + + for i, rv := range c.Triples.ReferenceValues.Values { + if err := extractQERefVal(rv); err != nil { + return fmt.Errorf("bad PSA reference value at index %d: %w", i, err) + } + } + + return nil +} + +func extractQERefVal(rv comid.ValueTriple) error { + class := rv.Environment.Class + + if err := extractClassElements(class); err != nil { + return fmt.Errorf("extracting class: %w", err) + } + + measurements := rv.Measurements + if err := extractQEMeasurements(&measurements); err != nil { + return fmt.Errorf("extracting measurements: %w", err) + } + + return nil +} + +func extractQEMeasurements(meas *comid.Measurements) error { + if len(meas.Values) == 0 { + return fmt.Errorf("no measurements") + } + for i := range meas.Values { + m := &meas.Values[0] + if err := decodeQEMValExtensions(m); err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + + if m.AuthorizedBy != nil { + err := decodeAuthorisedBy(m) + if err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + } + } + return nil +} + +func decodeQEMValExtensions(m *comid.Measurement) error { + val, err := m.Val.Extensions.Get("miscselect") + if err != nil { + return fmt.Errorf("failed to decode miscselect from measurement extensions") + } + f, ok := val.(*TeeMiscSelect) + if !ok { + fmt.Printf("val was not pointer to TeeMiscSelect") + } + miscselect := *f + fmt.Printf("\nmiscselect: %x", miscselect) + + val, err = m.Val.Extensions.Get("tcbevalnum") + if err != nil { + return fmt.Errorf("failed to decode tcbevalnum from measurement extensions") + } + t, ok := val.(*TeeTcbEvalNum) + if !ok { + fmt.Printf("val was not pointer to TeeTcbEvalNum") + } + tcbValNum := *t + fmt.Printf("\ntcbEvalNum: %d", tcbValNum) + + val, err = m.Val.Extensions.Get("isvprodid") + if err != nil { + return fmt.Errorf("failed to decode isvprodid from measurement extensions") + } + tS, ok := val.(*TeeISVProdID) + if !ok { + fmt.Printf("val was not pointer to IsvProdID") + } + + if tS.IsBytes() { + val, err = tS.GetBytes() + if err != nil { + return fmt.Errorf("failed to decode isvprodid: %w", err) + } + fmt.Printf("\nIsvProdID: %x", val) + } else if tS.IsUint() { + val, err = tS.GetUint() + if err != nil { + return fmt.Errorf("failed to decode isvprodid: %w", err) + } + fmt.Printf("\nIsvProdID: %d", val) + } else { + return fmt.Errorf("isvprodid is neither integer or byte string") + } + + val, err = m.Val.Extensions.Get("mrsigner") + if err != nil { + return fmt.Errorf("failed to decode mrsigner from measurement extensions") + } + + tD, ok := val.(*TeeDigest) + if !ok { + fmt.Printf("val was not pointer to TeeDigest") + } + + err = extractDigest("mrsigner", tD) + if err != nil { + return fmt.Errorf("unable to extract TEE Digest: %w", err) + } + return nil +} + +func Example_encode_tdx_QE_refval_without_profile() { + refVal := &comid.ValueTriple{} + measurement := &comid.Measurement{} + refVal.Environment = comid.Environment{ + Class: comid.NewClassOID(TestOID). + SetVendor("Intel Corporation"). + SetModel("0123456789ABCDEF"), // From irim-qe-cend.diag, CPUID[0x01].EAX.FMSP & 0x0FFF0FF0 + } + + extMap := extensions.NewMap(). + Add(comid.ExtReferenceValue, &MValExtensions{}) + + m := comid.NewComid(). + SetTagIdentity("43BBE37F-2E61-4B33-AED3-53CFF1428B20", 0). + AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) + + refVal.Measurements.Add(measurement) + m.Triples.AddReferenceValue(*refVal) + if err := m.RegisterExtensions(extMap); err != nil { + panic(err) + } + + if err := setTDXQEMvalExtensions(&m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val); err != nil { + panic(err) + } + if err := m.Valid(); err != nil { + panic(err) + } + + cbor, err := m.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } else { + fmt.Printf("To CBOR failed \n") + } + + json, err := m.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } else { + fmt.Printf("To JSON failed \n") + } + + // Output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02703031323334353637383941424344454681a101a538480a385046c000fbff000038538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638540138550b + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":10,"miscselect":"wAD7/wAA","mrsigner":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"],"isvprodid":{"type":"uint","value":1},"tcbevalnum":11}}]}]}} +} + +func setTDXQEMvalExtensions(val *comid.Mval) error { + svn := TeeSVN(10) + teeTcbEvNum := TeeTcbEvalNum(11) + teeMiscSel := TeeMiscSelect([]byte{0xC0, 0x00, 0xFB, 0xFF, 0x00, 0x00}) // Taken from irim-qe-ref.diag + // Taken below from irim-qe-ref.diag + r := 1 + isvProdID, err := NewTeeISVProdID(r) + if err != nil { + return fmt.Errorf("unable to get isvprodid %w", err) + } + + err = val.Extensions.Set("isvprodid", isvProdID) + if err != nil { + return fmt.Errorf("unable to set isvprodid %w", err) + } + err = val.Extensions.Set("isvsvn", &svn) + if err != nil { + return fmt.Errorf("unable to set isvsvn %w", err) + } + err = val.Extensions.Set("tcbevalnum", &teeTcbEvNum) + if err != nil { + return fmt.Errorf("unable to set tcbevalnum %w", err) + } + err = val.Extensions.Set("miscselect", &teeMiscSel) + if err != nil { + return fmt.Errorf("unable to set miscselect %w", err) + } + + d := comid.NewDigests() + d.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) + d.AddDigest(swid.Sha384, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) + + err = val.Extensions.Set("mrsigner", d) + if err != nil { + return fmt.Errorf("unable to set mrsigner %w", err) + } + return nil +} + +var ( + // test cases are based on diag files here: + // https://github.com/ietf-rats-wg/draft-ietf-rats-corim/tree/main/cddl/examples + + //go:embed testcases/comid_qe_refval.cbor + testComid2 []byte +) + +func Example_decode_QE_CBOR() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + manifest, found := corim.GetProfileManifest(profileID) + if !found { + fmt.Printf("CoRIM Profile NOT FOUND") + return + } + + m := manifest.GetComid() + + if err := m.FromCBOR(testComid2); err != nil { + panic(err) + } + if err := m.Valid(); err != nil { + panic(err) + } + + if err := extractQERefVals(m); err != nil { + panic(err) + } + + // output: + // OID: 2.16.840.1.113741.1.2.3.4.1 + // Vendor: Intel Corporation + // Model: SGX QE TCB + // miscselect: a0b0c0d000000000 + // tcbEvalNum: 11 + // IsvProdID: 1 + // mrsigner Digest Alg: 1 + // mrsigner Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a + // mrsigner Digest Alg: 8 + // mrsigner Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a + // CryptoKey Type: pkix-base64-key + // CryptoKey Value: -----BEGIN PUBLIC KEY----- + // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== + // -----END PUBLIC KEY----- +} diff --git a/comid/tdx-profile/example_seam_refval_test.go b/comid/tdx-profile/example_seam_refval_test.go new file mode 100644 index 00000000..9898b7a8 --- /dev/null +++ b/comid/tdx-profile/example_seam_refval_test.go @@ -0,0 +1,516 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + _ "embed" + "fmt" + "log" + "time" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" + "github.com/veraison/swid" +) + +// Example_decode_JSON decodes the TDX Measurement Extensions from the given JSON Template +func Example_decode_JSON() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + manifest, found := corim.GetProfileManifest(profileID) + if !found { + fmt.Printf("CoRIM Profile NOT FOUND") + return + } + + m := manifest.GetComid() + if err := m.FromJSON([]byte(TDXSeamRefValJSONTemplate)); err != nil { + panic(err) + } + + if err := m.Valid(); err != nil { + panic(err) + } + + if err := extractRefVals(m); err != nil { + panic(err) + } + + // output: + // OID: 2.16.840.1.113741.1.2.3.4.5 + // Vendor: Intel Corporation + // Model: TDX SEAM + // tcbEvalNum: 11 + // IsvProdID: 0303 + // ISVSVN: 10 + // Attributes: f00a0b + // mrtee Digest Alg: 1 + // mrtee Digest Value: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 + // mrsigner Digest Alg: 1 + // mrsigner Digest Value: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 + // mrsigner Digest Alg: 8 + // mrsigner Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a + // CryptoKey Type: pkix-base64-key + // CryptoKey Value: -----BEGIN PUBLIC KEY----- + // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== + // -----END PUBLIC KEY----- +} + +func Example_encode_tdx_seam_refval_without_profile() { + refVal := &comid.ValueTriple{} + measurement := &comid.Measurement{} + refVal.Environment = comid.Environment{ + Class: comid.NewClassOID(TestOID). + SetVendor("Intel Corporation"). + SetModel("TDXSEAM"), + } + + extMap := extensions.NewMap(). + Add(comid.ExtReferenceValue, &MValExtensions{}) + + m := comid.NewComid(). + SetTagIdentity("43BBE37F-2E61-4B33-AED3-53CFF1428B20", 0). + AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) + + refVal.Measurements.Add(measurement) + m.Triples.AddReferenceValue(*refVal) + if err := m.RegisterExtensions(extMap); err != nil { + panic(err) + } + + if err := setTDXSeamMvalExtensions(&m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val); err != nil { + panic(err) + } + if err := m.Valid(); err != nil { + panic(err) + } + + cbor, err := m.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } else { + fmt.Printf("To CBOR failed \n") + } + + json, err := m.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } else { + fmt.Printf("To JSON failed \n") + } + + // Output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc8038480a385142010138528182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7538538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36385442010138550b + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":10,"attributes":"AQE=","mrtee":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="],"mrsigner":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"],"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":11}}]}]}} +} + +func Example_encode_tdx_seam_refval_with_profile() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + manifest, found := corim.GetProfileManifest(profileID) + if !found { + fmt.Printf("CoRIM Profile NOT FOUND") + return + } + + m := manifest.GetComid() + if m == nil { + panic(err) + } + m.SetTagIdentity("43BBE37F-2E61-4B33-AED3-53CFF1428B20", 0). + AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) + + refVal := &comid.ValueTriple{} + measurement := &comid.Measurement{} + refVal.Environment = comid.Environment{ + Class: comid.NewClassOID(TestOID). + SetVendor("Intel Corporation"). + SetModel("TDXSEAM"), + } + + refVal.Measurements.Add(measurement) + m.Triples.AddReferenceValue(*refVal) + + err = setTDXSeamMvalExtensions(&m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val) + if err != nil { + fmt.Printf("unable to set extensions :%s", err.Error()) + } + + err = m.Valid() + if err != nil { + fmt.Printf("CoMID is not Valid :%s", err.Error()) + } + + cbor, err := m.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } else { + fmt.Printf("\n To CBOR Failed: %s \n", err.Error()) + } + + json, err := m.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } else { + fmt.Printf("\n To JSON Failed \n") + } + + // Output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc8038480a385142010138528182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7538538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36385442010138550b + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":10,"attributes":"AQE=","mrtee":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="],"mrsigner":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"],"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":11}}]}]}} +} + +func Example_encode_tdx_seam_refval_direct() { + refVal := &comid.ValueTriple{} + measurement := &comid.Measurement{} + refVal.Environment = comid.Environment{ + Class: comid.NewClassOID(TestOID). + SetVendor("Intel Corporation"). + SetModel("TDXSEAM"), + } + + extMap := extensions.NewMap().Add(comid.ExtMval, &MValExtensions{}) + m := comid.NewComid(). + SetTagIdentity("43BBE37F-2E61-4B33-AED3-53CFF1428B20", 0). + AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) + + if err := measurement.Val.RegisterExtensions(extMap); err != nil { + log.Fatal("could not register mval extensions") + } + + if err := setTDXSeamMvalExtensions(&measurement.Val); err != nil { + log.Fatal("could not set mval extensions") + } + + refVal.Measurements.Add(measurement) + m.Triples.AddReferenceValue(*refVal) + + err := m.Valid() + if err != nil { + fmt.Printf("CoMID is not Valid :%s", err.Error()) + } + + cbor, err := m.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } else { + fmt.Printf("\n To CBOR Failed: %s \n", err.Error()) + } + + json, err := m.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } else { + fmt.Printf("\n To JSON Failed \n") + } + + // Output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc8038480a385142010138528182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7538538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36385442010138550b + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":10,"attributes":"AQE=","mrtee":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="],"mrsigner":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"],"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":11}}]}]}} +} + +func setTDXSeamMvalExtensions(val *comid.Mval) error { + tcbDate, _ := time.Parse(time.RFC3339, "2025-01-27T00:00:00Z") + err := val.Extensions.Set("tcbdate", &tcbDate) + if err != nil { + return fmt.Errorf("unable to set tcbDate %w", err) + } + r := []byte{0x01, 0x01} + isvProdID, err := NewTeeISVProdID(r) + if err != nil { + return fmt.Errorf("unable to get isvprodid %w", err) + } + err = val.Extensions.Set("isvprodid", isvProdID) + if err != nil { + return fmt.Errorf("unable to set isvprodid %w", err) + } + svn := TeeSVN(TestISVSVN) + err = val.Extensions.Set("isvsvn", &svn) + if err != nil { + return fmt.Errorf("unable to set isvsvn %w", err) + } + teeTcbEvNum := TeeTcbEvalNum(TestTCBEvalNum) + err = val.Extensions.Set("tcbevalnum", &teeTcbEvNum) + if err != nil { + return fmt.Errorf("unable to set tcbevalnum %w", err) + } + + teeAttr, err := NewTeeAttributes(TestTeeAttributes) + if err != nil { + return fmt.Errorf("unable to get teeAttributes %w", err) + } + err = val.Extensions.Set("attributes", teeAttr) + if err != nil { + return fmt.Errorf("unable to set attributes %w", err) + } + + d := comid.NewDigests() + d.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) + err = val.Extensions.Set("mrtee", d) + if err != nil { + return fmt.Errorf("unable to set mrtee %w", err) + } + + d = comid.NewDigests() + d.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) + d.AddDigest(swid.Sha384, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) + + err = val.Extensions.Set("mrsigner", d) + if err != nil { + return fmt.Errorf("unable to set mrsigner %w", err) + } + return nil +} + +func decodeMValExtensions(m *comid.Measurement) error { + val, err := m.Val.Extensions.Get("tcbevalnum") + if err != nil { + return fmt.Errorf("failed to decode tcbevalnum from measurement extensions") + } + f, ok := val.(*TeeTcbEvalNum) + if !ok { + fmt.Printf("val was not pointer to TeeTcbEvalNum") + } + tcbValNum := *f + fmt.Printf("\ntcbEvalNum: %d", tcbValNum) + + val, err = m.Val.Extensions.Get("isvprodid") + if err != nil { + return fmt.Errorf("failed to decode isvprodid from measurement extensions") + } + tS, ok := val.(*TeeISVProdID) + if !ok { + fmt.Printf("val was not pointer to IsvProdID") + } + if tS.IsBytes() { + val, err = tS.GetBytes() + if err != nil { + return fmt.Errorf("failed to decode isvprodid: %w", err) + } + fmt.Printf("\nIsvProdID: %x", val) + } else if tS.IsUint() { + val, err = tS.GetUint() + if err != nil { + return fmt.Errorf("failed to decode isvprodid: %w", err) + } + fmt.Printf("\nIsvProdID: %d", val) + } else { + return fmt.Errorf("isvprodid is neither integer or byte string") + } + + val, err = m.Val.Extensions.Get("isvsvn") + if err != nil { + return fmt.Errorf("failed to decode isvsvn from measurement extensions") + } + tSV, ok := val.(*TeeSVN) + if !ok { + fmt.Printf("val was not pointer to tee svn") + } + + fmt.Printf("\nISVSVN: %d", *tSV) + + val, err = m.Val.Extensions.Get("attributes") + if err != nil { + return fmt.Errorf("failed to decode attributes from measurement extensions") + } + + tA, ok := val.(*TeeAttributes) + if !ok { + fmt.Printf("val was not pointer to teeAttributes") + } + + fmt.Printf("\nAttributes: %x", *tA) + + val, err = m.Val.Extensions.Get("mrtee") + if err != nil { + return fmt.Errorf("failed to decode mrtee from measurement extensions") + } + + tD, ok := val.(*TeeDigest) + if !ok { + fmt.Printf("val was not pointer to TeeDigest") + } + + err = extractDigest("mrtee", tD) + if err != nil { + return fmt.Errorf("unable to extract TEE Digest: %w", err) + } + + val, err = m.Val.Extensions.Get("mrsigner") + if err != nil { + return fmt.Errorf("failed to decode mrsigner from measurement extensions") + } + + tD, ok = val.(*TeeDigest) + if !ok { + fmt.Printf("val was not pointer to TeeDigest") + } + + err = extractDigest("mrsigner", tD) + if err != nil { + return fmt.Errorf("unable to extract TEE Digest: %w", err) + } + return nil +} + +func decodeAuthorisedBy(m *comid.Measurement) error { + if err := m.AuthorizedBy.Valid(); err != nil { + return fmt.Errorf("invalid cryptokey: %w", err) + } + fmt.Printf("\nCryptoKey Type: %s", m.AuthorizedBy.Type()) + fmt.Printf("\nCryptoKey Value: %s", m.AuthorizedBy.String()) + return nil +} + +var ( + // test cases are based on diag files here: + // https://github.com/ietf-rats-wg/draft-ietf-rats-corim/tree/main/cddl/examples + + //go:embed testcases/comid_seam_refval.cbor + testComid1 []byte +) + +func Example_decode_CBOR() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + manifest, found := corim.GetProfileManifest(profileID) + if !found { + fmt.Printf("CoRIM Profile NOT FOUND") + return + } + + m := manifest.GetComid() + + if err := m.FromCBOR(testComid1); err != nil { + panic(err) + } + if err := m.Valid(); err != nil { + panic(err) + } + + if err := extractRefVals(m); err != nil { + panic(err) + } + + // output: + // OID: 2.16.840.1.113741.1.2.3.4.3 + // Vendor: Intel Corporation + // Model: TDX SEAM + // tcbEvalNum: 11 + // IsvProdID: abcd + // ISVSVN: 6 + // Attributes: 0102 + // mrtee Digest Alg: 1 + // mrtee Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a + // mrsigner Digest Alg: 1 + // mrsigner Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a + // mrsigner Digest Alg: 8 + // mrsigner Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a + // CryptoKey Type: pkix-base64-key + // CryptoKey Value: -----BEGIN PUBLIC KEY----- + // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== + // -----END PUBLIC KEY----- +} + +func extractRefVals(c *comid.Comid) error { + if c.Triples.ReferenceValues == nil { + return fmt.Errorf("no reference values triples") + } + + for i, rv := range c.Triples.ReferenceValues.Values { + if err := extractSeamRefVal(rv); err != nil { + return fmt.Errorf("bad reference value at index %d: %w", i, err) + } + } + return nil +} + +func extractSeamRefVal(rv comid.ValueTriple) error { + class := rv.Environment.Class + + if err := extractClassElements(class); err != nil { + return fmt.Errorf("extracting class: %w", err) + } + + measurements := rv.Measurements + if err := extractSeamMeasurements(&measurements); err != nil { + return fmt.Errorf("extracting measurements: %w", err) + } + + return nil +} + +func extractSeamMeasurements(meas *comid.Measurements) error { + if len(meas.Values) == 0 { + return fmt.Errorf("no measurements") + } + for i := range meas.Values { + m := &meas.Values[0] + if err := decodeMValExtensions(m); err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + + if m.AuthorizedBy != nil { + err := decodeAuthorisedBy(m) + if err != nil { + return fmt.Errorf("extracting measurement at index %d: %w", i, err) + } + } + } + return nil +} + +func extractClassElements(c *comid.Class) error { + if c == nil { + return fmt.Errorf("no class") + } + + classID := c.ClassID + + if classID == nil { + return fmt.Errorf("no class-id") + } + + if classID.Type() != comid.OIDType { + return fmt.Errorf("class id is not an oid") + } + + fmt.Printf("OID: %s", classID.Value.String()) + + if c.Vendor == nil { + return fmt.Errorf("no Vendor") + } + fmt.Printf("\nVendor: %s", *c.Vendor) + + if c.Model == nil { + return fmt.Errorf("no Model") + } + fmt.Printf("\nModel: %s", *c.Model) + + return nil +} + +func extractDigest(typ string, d *TeeDigest) error { + if d == nil { + return fmt.Errorf("no TEE digest") + } + + if typ != "mrsigner" && typ != "mrtee" { + return fmt.Errorf("invalid type for TEE digest: %s", typ) + } + for _, digest := range *d { + fmt.Printf("\n%s Digest Alg: %d", typ, digest.HashAlgID) + fmt.Printf("\n%s Digest Value: %x", typ, digest.HashValue) + } + + return nil +} diff --git a/comid/tdx-profile/mval_extensions.go b/comid/tdx-profile/mval_extensions.go new file mode 100644 index 00000000..af4e0f2e --- /dev/null +++ b/comid/tdx-profile/mval_extensions.go @@ -0,0 +1,60 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "time" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" +) + +// MValExtensions contains the Intel TDX profile extensions which can appear in +// both Reference Values and Endorsed Values +type MValExtensions struct { + TeeTcbDate *time.Time `cbor:"-72,keyasint,omitempty" json:"tcbdate,omitempty"` + TeeISVSVN *TeeSVN `cbor:"-73,keyasint,omitempty" json:"isvsvn,omitempty"` + TeeInstanceID *TeeInstanceID `cbor:"-77,keyasint,omitempty" json:"instanceid,omitempty"` + TeePCEID *TeePCEID `cbor:"-80,keyasint,omitempty" json:"pceid,omitempty"` + TeeMiscSelect *TeeMiscSelect `cbor:"-81,keyasint,omitempty" json:"miscselect,omitempty"` + TeeAttributes *TeeAttributes `cbor:"-82,keyasint,omitempty" json:"attributes,omitempty"` + TeeMrTee *TeeDigest `cbor:"-83,keyasint,omitempty" json:"mrtee,omitempty"` + TeeMrSigner *TeeDigest `cbor:"-84,keyasint,omitempty" json:"mrsigner,omitempty"` + TeeISVProdID *TeeISVProdID `cbor:"-85,keyasint,omitempty" json:"isvprodid,omitempty"` + TeeTcbEvalNum *TeeTcbEvalNum `cbor:"-86,keyasint,omitempty" json:"tcbevalnum,omitempty"` + TeeTcbStatus *TeeTcbStatus `cbor:"-88,keyasint,omitempty" json:"tcbstatus,omitempty"` + TeeAdvisoryIDs *TeeAdvisoryIDs `cbor:"-89,keyasint,omitempty" json:"advisoryids,omitempty"` + TeeEpoch *time.Time `cbor:"-90, keyasint,omitempty" json:"epoch,omitempty"` + + TeeCryptoKeys *comid.CryptoKeys `cbor:"-91, keyasint,omitempty" json:"teecryptokeys,omitempty"` + TeeTCBCompSvn *TeeTcbCompSvn `cbor:"-125, keyasint,omitempty" json:"tcbcompsvn,omitempty"` +} + +// Registering the profile inside init() in the same file where it is defined +// ensures that the profile will always be available, and you don't need to +// remember to register it when you want to use it. The only potential +// danger with that is if your profile ID clashes with another profile, +// which should not happen if it is a registered PEN or a URL containing a domain +// that you own. +// Note Intel profile is "2.16.840.1.113741.1.16.1", +// which is "joint-iso-itu-t.country.us.organization.intel.intel-comid.profile" + +func init() { + profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") + if err != nil { + panic(err) // will not error, as the hard-coded string above is valid + } + + extMap := extensions.NewMap(). + Add(comid.ExtReferenceValue, &MValExtensions{}). + Add(comid.ExtEndorsedValue, &MValExtensions{}) + + if err := corim.RegisterProfile(profileID, extMap); err != nil { + // will not error, assuming our profile ID is unique, and we've + // correctly set up the extensions Map above + panic(err) + } +} diff --git a/comid/tdx-profile/teeadvisoryids.go b/comid/tdx-profile/teeadvisoryids.go new file mode 100644 index 00000000..7662e799 --- /dev/null +++ b/comid/tdx-profile/teeadvisoryids.go @@ -0,0 +1,61 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// nolint:dupl +package tdx + +import "fmt" + +type TeeAdvisoryIDs setType + +// NewTeeAvisoryIDs create a new TeeAvisoryIDs from the +// supplied interface array and returns a pointer to +// the AdvisoryIDs. Only +// Advisory IDs of string type are supported +func NewTeeAvisoryIDs(val []any) (*TeeAdvisoryIDs, error) { + var adv TeeAdvisoryIDs + if len(val) == 0 { + return nil, fmt.Errorf("zero len TeeAdvisoryIDs") + } + + for i, v := range val { + switch t := v.(type) { + case string: + adv = append(adv, t) + default: + return nil, fmt.Errorf("invalid type: %T for AdvisoryIDs at index: %d", t, i) + } + } + return &adv, nil +} + +// AddTeeAdvisoryIDs add supplied AvisoryIDs to existing AdvisoryIDs +func (o *TeeAdvisoryIDs) AddTeeAdvisoryIDs(val []any) error { + for i, v := range val { + switch t := v.(type) { + case string: + *o = append(*o, t) + default: + return fmt.Errorf("invalid type: %T for AdvisoryIDs at index: %d", t, i) + } + } + return nil +} + +// Valid checks for validity of TeeAdvisoryIDs and +// returns an error, if invalid +func (o TeeAdvisoryIDs) Valid() error { + if len(o) == 0 { + return fmt.Errorf("empty AdvisoryIDs") + + } + for i, v := range o { + switch t := v.(type) { + case string: + continue + default: + return fmt.Errorf("invalid type: %T for AdvisoryIDs at index: %d", t, i) + } + } + return nil +} diff --git a/comid/tdx-profile/teeadvisoryids_test.go b/comid/tdx-profile/teeadvisoryids_test.go new file mode 100644 index 00000000..01785989 --- /dev/null +++ b/comid/tdx-profile/teeadvisoryids_test.go @@ -0,0 +1,78 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func initAdvisoryIDs() []any { + s := make([]any, len(TestAdvisoryIDs)) + for i := range TestAdvisoryIDs { + s[i] = TestAdvisoryIDs[i] + } + return s +} + +func TestAdvisoryIDs_NewTeeAvisoryIDs_OK(t *testing.T) { + a := initAdvisoryIDs() + _, err := NewTeeAvisoryIDs(a) + require.Nil(t, err) +} + +func TestAdvisoryIDs_NewTeeAvisoryIDs_NOK(t *testing.T) { + expectedErr := "invalid type: int for AdvisoryIDs at index: 0" + a := make([]any, len(TestAdvisoryIDs)) + for i := range TestAdvisoryIDs { + a[i] = i + } + _, err := NewTeeAvisoryIDs(a) + assert.EqualError(t, err, expectedErr) +} + +func TestAdvisoryIDs_AddAdvisoryIDs_OK(t *testing.T) { + a := initAdvisoryIDs() + adv := TeeAdvisoryIDs{} + err := adv.AddTeeAdvisoryIDs(a) + require.NoError(t, err) +} + +func TestAdvisoryIDs_AddAdvisoryIDs_NOK(t *testing.T) { + expectedErr := "invalid type: float64 for AdvisoryIDs at index: 0" + s := make([]any, len(TestInvalidAdvisoryIDs)) + for i := range TestInvalidAdvisoryIDs { + s[i] = TestInvalidAdvisoryIDs[i] + } + adv := TeeAdvisoryIDs{} + err := adv.AddTeeAdvisoryIDs(s) + assert.EqualError(t, err, expectedErr) +} + +func TestAdvisoryIDs_Valid_OK(t *testing.T) { + a := initAdvisoryIDs() + adv, err := NewTeeAvisoryIDs(a) + require.NoError(t, err) + err = adv.Valid() + require.NoError(t, err) +} + +func TestAdvisoryIDs_Valid_NOK(t *testing.T) { + expectedErr := "empty AdvisoryIDs" + adv := TeeAdvisoryIDs{} + err := adv.Valid() + assert.EqualError(t, err, expectedErr) + + expectedErr = "invalid type: float64 for AdvisoryIDs at index: 0" + s := make([]any, len(TestInvalidAdvisoryIDs)) + for i := range TestInvalidAdvisoryIDs { + s[i] = TestInvalidAdvisoryIDs[i] + } + adv = TeeAdvisoryIDs(s) + err = adv.Valid() + assert.EqualError(t, err, expectedErr) + +} diff --git a/comid/tdx-profile/teeattributes.go b/comid/tdx-profile/teeattributes.go new file mode 100644 index 00000000..64cdd0f7 --- /dev/null +++ b/comid/tdx-profile/teeattributes.go @@ -0,0 +1,27 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// nolint:dupl +package tdx + +import "fmt" + +type TeeAttributes maskType + +func NewTeeAttributes(val []byte) (*TeeAttributes, error) { + if val == nil { + return nil, fmt.Errorf("nil TeeAttributes") + } + teeAttributes := TeeAttributes(val) + return &teeAttributes, nil +} + +func (o TeeAttributes) Valid() error { + if o == nil { + return fmt.Errorf("nil TeeAttributes") + } + if len(o) == 0 { + return fmt.Errorf("zero len TeeAttributes") + } + return nil +} diff --git a/comid/tdx-profile/teeattributes_test.go b/comid/tdx-profile/teeattributes_test.go new file mode 100644 index 00000000..c5734f23 --- /dev/null +++ b/comid/tdx-profile/teeattributes_test.go @@ -0,0 +1,35 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewTeeAttributes_NewTeeAttributes_OK(t *testing.T) { + _, err := NewTeeAttributes(TestTeeAttributes) + require.Nil(t, err) +} + +func TestNewTeeAttributes_NewTeeAttributes_NOK(t *testing.T) { + expectedErr := "nil TeeAttributes" + _, err := NewTeeAttributes(nil) + assert.EqualError(t, err, expectedErr) +} + +func TestNewTeeAttributes_Valid_OK(t *testing.T) { + tA := TeeAttributes(TestTeeAttributes) + err := tA.Valid() + require.Nil(t, err) +} + +func TestNewTeeAttributes_Valid_NOK(t *testing.T) { + tA := TeeAttributes{} + expectedErr := "zero len TeeAttributes" + err := tA.Valid() + assert.EqualError(t, err, expectedErr) +} diff --git a/comid/tdx-profile/teeinstanceid.go b/comid/tdx-profile/teeinstanceid.go new file mode 100644 index 00000000..8d9cbae0 --- /dev/null +++ b/comid/tdx-profile/teeinstanceid.go @@ -0,0 +1,179 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// nolint:dupl +package tdx + +import ( + "encoding/json" + "fmt" + + "github.com/fxamacker/cbor/v2" + "github.com/veraison/corim/encoding" +) + +// TeeInstanceID stores an TEE Instance Identifier. The supported formats are uint and variable-length bytes. +type TeeInstanceID struct { + val interface{} +} + +// NewTeeInstanceID creates a new InstanceID from the +// supplied interface. The supported types are positive integers and +// byte array +func NewTeeInstanceID(val any) (*TeeInstanceID, error) { + switch t := val.(type) { + case uint, uint64: + return &TeeInstanceID{val: t}, nil + case []byte: + return &TeeInstanceID{val: t}, nil + case int: + if t < 0 { + return nil, fmt.Errorf("unsupported negative %d TeeInstanceID", t) + } + return &TeeInstanceID{val: t}, nil + default: + return nil, fmt.Errorf("unsupported TeeInstanceID type: %T", t) + } +} + +// SetTeeInstanceID sets the supplied value of Instance ID +func (o *TeeInstanceID) SetTeeInstanceID(val any) error { + switch t := val.(type) { + case uint, uint64: + o.val = val + case []byte: + o.val = val + case int: + if t < 0 { + return fmt.Errorf("unsupported negative TeeInstanceID: %d", t) + } + o.val = val + default: + return fmt.Errorf("unsupported TeeInstanceID type: %T", t) + } + return nil +} + +// valid checks for validity of TeeInstanceID and +// returns an error if Invalid +func (o TeeInstanceID) Valid() error { + if o.val == nil { + return fmt.Errorf("empty TeeInstanceID") + } + switch t := o.val.(type) { + case uint, uint64: + return nil + case []byte: + if len(t) == 0 { + return fmt.Errorf("empty TeeInstanceID") + } + case int: + if t < 0 { + return fmt.Errorf("unsupported negative TeeInstanceID: %d", t) + } + default: + return fmt.Errorf("unsupported TeeInstanceID type: %T", t) + } + return nil +} + +// GetUint returns unsigned integer TeeInstanceID +func (o TeeInstanceID) GetUint() (uint, error) { + switch t := o.val.(type) { + case uint64: + return uint(t), nil + case uint: + return t, nil + default: + return 0, fmt.Errorf("TeeInstanceID type is: %T", t) + } +} + +// GetBytes returns the bytes TeeInstanceID +func (o TeeInstanceID) GetBytes() ([]byte, error) { + switch t := o.val.(type) { + case []byte: + if len(t) == 0 { + return nil, fmt.Errorf("TeeInstanceID type is of zero length") + } + return t, nil + default: + return nil, fmt.Errorf("TeeInstanceID type is: %T", t) + } +} + +// IsBytes returns true if TeeInstanceID is of type []byte array +func (o TeeInstanceID) IsBytes() bool { + return isType[[]byte](o.val) +} + +// IsUnit returns true if TeeInstanceID is of type unsigned integer +func (o TeeInstanceID) IsUint() bool { + return isType[uint64](o.val) || isType[uint](o.val) +} + +// MarshalJSON Marshals TeeInstanceID to JSON +func (o TeeInstanceID) MarshalJSON() ([]byte, error) { + if o.Valid() != nil { + return nil, fmt.Errorf("invalid TeeInstanceID") + } + var ( + v encoding.TypeAndValue + b []byte + err error + ) + switch t := o.val.(type) { + case uint, uint64, int: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: "uint", Value: b} + case []byte: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: "bytes", Value: b} + default: + return nil, fmt.Errorf("unknown type %T for TeeInstanceID", t) + } + return json.Marshal(v) +} + +// UnmarshalJSON UnMarshals supplied JSON bytes to TeeInstanceID +func (o *TeeInstanceID) UnmarshalJSON(data []byte) error { + var v encoding.TypeAndValue + + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + switch v.Type { + case "uint": + var x uint + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeInstanceID of type uint: %w", err) + } + o.val = x + case "bytes": + var x []byte + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeInstanceID of type bytes: %w", err) + } + o.val = x + } + return nil +} + +// MarshalCBOR Marshals TeeInstanceID to CBOR +func (o TeeInstanceID) MarshalCBOR() ([]byte, error) { + return cbor.Marshal(o.val) +} + +// UnmarshalCBOR UnMarshals supplied CBOR bytes to TeeInstanceID +func (o *TeeInstanceID) UnmarshalCBOR(data []byte) error { + return cbor.Unmarshal(data, &o.val) +} diff --git a/comid/tdx-profile/teeinstanceid_test.go b/comid/tdx-profile/teeinstanceid_test.go new file mode 100644 index 00000000..8e976080 --- /dev/null +++ b/comid/tdx-profile/teeinstanceid_test.go @@ -0,0 +1,118 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" +) + +func TestTeeInstanceID_NewTeeInstanceID_OK(t *testing.T) { + tvs := []struct { + desc string + input interface{} + }{ + { + desc: "integer", + input: TestUIntInstance, + }, + { + desc: "byte array", + input: TestByteInstance, + }, + { + desc: "unsigned integer 64", + input: uint64(TestUIntInstance), + }, + } + + for _, tv := range tvs { + _, err := NewTeeInstanceID(tv.input) + require.Nil(t, err) + } +} + +func TestTeeInstanceID_SetTeeInstanceID_OK(t *testing.T) { + inst := &TeeInstanceID{} + err := inst.SetTeeInstanceID(TestUIntInstance) + require.NoError(t, err) + err = inst.SetTeeInstanceID(TestByteInstance) + require.NoError(t, err) + err = inst.SetTeeInstanceID(uint64(1000)) + require.NoError(t, err) +} + +func TestTeeInstanceID_SetTeeInstanceID_NOK(t *testing.T) { + inst := &TeeInstanceID{} + expectedErr := "unsupported negative TeeInstanceID: -1" + err := inst.SetTeeInstanceID(-1) + assert.EqualError(t, err, expectedErr) + expectedErr = "unsupported TeeInstanceID type: float64" + err = inst.SetTeeInstanceID(-1.234) + assert.EqualError(t, err, expectedErr) +} + +func TestTeeInstanceID_Valid_OK(t *testing.T) { + inst := &TeeInstanceID{TestUIntInstance} + err := inst.Valid() + require.NoError(t, err) +} + +func TestTeeInstanceID_Valid_NOK(t *testing.T) { + tvs := []struct { + desc string + input interface{} + expectedErr string + }{ + { + desc: "unsupported type negative integer", + input: -1, + expectedErr: "unsupported negative TeeInstanceID: -1", + }, + { + desc: "non existent TeeInstanceID", + input: nil, + expectedErr: "empty TeeInstanceID", + }, + { + desc: "non existent TeeInstanceID", + input: []byte{}, + expectedErr: "empty TeeInstanceID", + }, + { + desc: "unsupported type float64", + input: 1.234, + expectedErr: "unsupported TeeInstanceID type: float64", + }, + } + + for _, tv := range tvs { + inst := &TeeInstanceID{tv.input} + err := inst.Valid() + assert.EqualError(t, err, tv.expectedErr) + } +} + +func TestTeeInstanceID_MarshalCBOR_Bytes(t *testing.T) { + inst, err := NewTeeInstanceID(TestByteInstance) + require.Nil(t, err) + expected := comid.MustHexDecode(t, "43454647") + actual, err := inst.MarshalCBOR() + fmt.Printf("CBOR: %x\n", actual) + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +func TestTeeInstanceID_JSON(t *testing.T) { + inst := TeeInstanceID{TestByteInstance} + ji, err := inst.MarshalJSON() + assert.Nil(t, err) + i := &TeeInstanceID{} + err = i.UnmarshalJSON(ji) + assert.Nil(t, err) +} diff --git a/comid/tdx-profile/teeisvproid.go b/comid/tdx-profile/teeisvproid.go new file mode 100644 index 00000000..c8382d88 --- /dev/null +++ b/comid/tdx-profile/teeisvproid.go @@ -0,0 +1,179 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// nolint:dupl +package tdx + +import ( + "encoding/json" + "fmt" + + "github.com/fxamacker/cbor/v2" + "github.com/veraison/corim/encoding" +) + +// TeeISVProdID stores an ISV Product Identifier. The supported formats are uint and variable-length bytes. +type TeeISVProdID struct { + val interface{} +} + +// NewTeeISVProdID creates a new TeeISVProdID from the +// supplied interface and return a pointer to TeeISVProdID +// Supported values are positive integers and byte array +func NewTeeISVProdID(val interface{}) (*TeeISVProdID, error) { + switch t := val.(type) { + case uint, uint64: + return &TeeISVProdID{val: t}, nil + case []byte: + return &TeeISVProdID{val: t}, nil + case int: + if t < 0 { + return nil, fmt.Errorf("negative integer %d for TeeISVProdID", t) + } + return &TeeISVProdID{val: t}, nil + default: + return nil, fmt.Errorf("unsupported TeeISVProdID type: %T", t) + } +} + +// SetTeeISVProdID sets the supplied value of TeeISVProdID from the interface +// Supported values are either positive integers or byte array +func (o *TeeISVProdID) SetTeeISVProdID(val interface{}) error { + switch t := val.(type) { + case uint, uint64: + o.val = val + case []byte: + o.val = val + case int: + if t < 0 { + return fmt.Errorf("unsupported negative TeeISVProdID: %d", t) + } + o.val = val + default: + return fmt.Errorf("unsupported TeeISVProdID type: %T", t) + } + return nil +} + +// Valid checks for validity of TeeISVProdID and returns an error if Invalid +func (o TeeISVProdID) Valid() error { + if o.val == nil { + return fmt.Errorf("empty TeeISVProdID") + } + switch t := o.val.(type) { + case uint, uint64: + return nil + case []byte: + if len(t) == 0 { + return fmt.Errorf("empty TeeISVProdID") + } + case int: + if t < 0 { + return fmt.Errorf("unsupported negative TeeISVProdID: %d", t) + } + default: + return fmt.Errorf("unsupported TeeISVProdID type: %T", t) + } + return nil +} + +// GetUint returns a uint TeeISVProdID +func (o TeeISVProdID) GetUint() (uint, error) { + switch t := o.val.(type) { + case uint64: + return uint(t), nil + case uint: + return t, nil + default: + return 0, fmt.Errorf("TeeISVProdID type is: %T", t) + } +} + +// GetBytes returns a []byte TeeISVProdID +func (o TeeISVProdID) GetBytes() ([]byte, error) { + switch t := o.val.(type) { + case []byte: + if len(t) == 0 { + return nil, fmt.Errorf("TeeISVProdID type is of zero length") + } + return t, nil + default: + return nil, fmt.Errorf("TeeIsvProdID type is: %T", t) + } +} + +// IsBytes returns true if TeeISVProdID is a byte array +func (o TeeISVProdID) IsBytes() bool { + return isType[[]byte](o.val) +} + +// IsUint returns true if TeeISVProdID is a positive integer +func (o TeeISVProdID) IsUint() bool { + return isType[uint64](o.val) || isType[uint](o.val) +} + +// MarshalJSON Marshals TeeISVProdID to JSON +func (o TeeISVProdID) MarshalJSON() ([]byte, error) { + if o.Valid() != nil { + return nil, fmt.Errorf("invalid TeeISVProdID") + } + var ( + v encoding.TypeAndValue + b []byte + err error + ) + switch t := o.val.(type) { + case uint, uint64, int: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: "uint", Value: b} + case []byte: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: "bytes", Value: b} + default: + return nil, fmt.Errorf("unknown type %T for TeeISVProdID", t) + } + return json.Marshal(v) +} + +// UnmarshalJSON UnMarshals supplied JSON buffer to TeeISVProdID +func (o *TeeISVProdID) UnmarshalJSON(data []byte) error { + var v encoding.TypeAndValue + + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + switch v.Type { + case "uint": + var x uint + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeISVProdID of type uint: %w", err) + } + o.val = x + case "bytes": + var x []byte + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeISVProdID of type bytes: %w", err) + } + o.val = x + } + return nil +} + +// MarshalCBOR Marshals TeeISVProdID to CBOR bytes +func (o TeeISVProdID) MarshalCBOR() ([]byte, error) { + return cbor.Marshal(o.val) +} + +// UnmarshalCBOR UnMarshals supplied CBOR bytes to TeeISVProdID +func (o *TeeISVProdID) UnmarshalCBOR(data []byte) error { + return cbor.Unmarshal(data, &o.val) +} diff --git a/comid/tdx-profile/teeisvproid_test.go b/comid/tdx-profile/teeisvproid_test.go new file mode 100644 index 00000000..110b6868 --- /dev/null +++ b/comid/tdx-profile/teeisvproid_test.go @@ -0,0 +1,121 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" +) + +func TestISVProdID_NewISVProdID_OK(t *testing.T) { + tvs := []struct { + desc string + input interface{} + }{ + { + desc: "integer", + input: TestUIntISVProdID, + }, + { + desc: "byte array", + input: TestBytesISVProdID, + }, + { + desc: "unsigned integer 64", + input: uint64(TestUIntISVProdID), + }, + } + + for _, tv := range tvs { + _, err := NewTeeISVProdID(tv.input) + require.Nil(t, err) + } +} + +func TestIsvProdID_SetTeeISVProdID_OK(t *testing.T) { + id := &TeeISVProdID{} + err := id.SetTeeISVProdID(TestUIntISVProdID) + require.NoError(t, err) + err = id.SetTeeISVProdID(TestBytesISVProdID) + require.NoError(t, err) + err = id.SetTeeISVProdID(uint64(1000)) + require.NoError(t, err) +} + +func TestIsvProdID_SetTeeISVProdID_NOK(t *testing.T) { + id := &TeeISVProdID{} + expectedErr := "unsupported negative TeeISVProdID: -1" + err := id.SetTeeISVProdID(-1) + assert.EqualError(t, err, expectedErr) + expectedErr = "unsupported TeeISVProdID type: float64" + err = id.SetTeeISVProdID(-1.234) + assert.EqualError(t, err, expectedErr) +} + +func TestIsvProdID_Valid_OK(t *testing.T) { + id := &TeeISVProdID{TestUIntISVProdID} + err := id.Valid() + require.NoError(t, err) + id = &TeeISVProdID{TestBytesISVProdID} + err = id.Valid() + require.NoError(t, err) +} + +func TestIsvProdID_Valid_NOK(t *testing.T) { + tvs := []struct { + desc string + input interface{} + expectedErr string + }{ + { + desc: "unsupported type negative integer", + input: -1, + expectedErr: "unsupported negative TeeISVProdID: -1", + }, + { + desc: "non existent TeeISVProdID", + input: nil, + expectedErr: "empty TeeISVProdID", + }, + { + desc: "non existent TeeISVProdID", + input: []byte{}, + expectedErr: "empty TeeISVProdID", + }, + { + desc: "unsupported type float64", + input: 1.234, + expectedErr: "unsupported TeeISVProdID type: float64", + }, + } + + for _, tv := range tvs { + id := &TeeISVProdID{tv.input} + err := id.Valid() + assert.EqualError(t, err, tv.expectedErr) + } +} + +func TestIsvProdID_MarshalCBOR_Bytes(t *testing.T) { + id, err := NewTeeISVProdID(TestBytesISVProdID) + require.Nil(t, err) + expected := comid.MustHexDecode(t, "43010203") + actual, err := id.MarshalCBOR() + fmt.Printf("CBOR: %x\n", actual) + assert.Nil(t, err) + assert.Equal(t, expected, actual) +} + +func TestIsvProdID_JSON(t *testing.T) { + isv := TeeISVProdID{TestBytesISVProdID} + jisv, err := isv.MarshalJSON() + assert.Nil(t, err) + i := &TeeISVProdID{} + err = i.UnmarshalJSON(jisv) + assert.Nil(t, err) +} diff --git a/comid/tdx-profile/teemiscselect.go b/comid/tdx-profile/teemiscselect.go new file mode 100644 index 00000000..bfc20ef1 --- /dev/null +++ b/comid/tdx-profile/teemiscselect.go @@ -0,0 +1,27 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import "fmt" + +type TeeMiscSelect maskType + +func NewTeeMiscSelect(val []byte) (*TeeMiscSelect, error) { + var miscSelect TeeMiscSelect + if val == nil { + return nil, fmt.Errorf("nil value for TeeMiscSelect") + } + miscSelect = TeeMiscSelect(val) + return &miscSelect, nil +} + +func (o TeeMiscSelect) Valid() error { + if o == nil { + return fmt.Errorf("nil TeeMiscSelect") + } + if len(o) == 0 { + return fmt.Errorf("zero len TeeMiscSelect") + } + return nil +} diff --git a/comid/tdx-profile/teemiscselect_test.go b/comid/tdx-profile/teemiscselect_test.go new file mode 100644 index 00000000..2a796d89 --- /dev/null +++ b/comid/tdx-profile/teemiscselect_test.go @@ -0,0 +1,35 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTeeMiscSelect_NewTeeMiscSelect_OK(t *testing.T) { + _, err := NewTeeMiscSelect(TestTeeMiscSelect) + require.Nil(t, err) +} + +func TestTeeMiscSelect_NewTeeMiscSelect_NOK(t *testing.T) { + expectedErr := "nil value for TeeMiscSelect" + _, err := NewTeeMiscSelect(nil) + assert.EqualError(t, err, expectedErr) +} + +func TestNewTeeMiscSelect_Valid_OK(t *testing.T) { + tA := TeeMiscSelect(TestTeeMiscSelect) + err := tA.Valid() + require.Nil(t, err) +} + +func TestTeeMiscSelect_Valid_NOK(t *testing.T) { + tA := TeeMiscSelect{} + expectedErr := "zero len TeeMiscSelect" + err := tA.Valid() + assert.EqualError(t, err, expectedErr) +} diff --git a/comid/tdx-profile/teepceid.go b/comid/tdx-profile/teepceid.go new file mode 100644 index 00000000..7c9e29bb --- /dev/null +++ b/comid/tdx-profile/teepceid.go @@ -0,0 +1,26 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "fmt" +) + +type TeePCEID string + +func NewTeePCEID(val string) (*TeePCEID, error) { + var pceID TeePCEID + if val == "" { + return nil, fmt.Errorf("null string for TeePCEID") + } + pceID = TeePCEID(val) + return &pceID, nil +} + +func (o TeePCEID) Valid() error { + if o == "" { + return fmt.Errorf("nil TeePCEID") + } + return nil +} diff --git a/comid/tdx-profile/teepceid_test.go b/comid/tdx-profile/teepceid_test.go new file mode 100644 index 00000000..927ab42d --- /dev/null +++ b/comid/tdx-profile/teepceid_test.go @@ -0,0 +1,36 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPCEID_NewTeePCEID_OK(t *testing.T) { + _, err := NewTeePCEID(TestPCEID) + require.NoError(t, err) +} + +func TestPCEID_NewTeePCEID_NOK(t *testing.T) { + expectedErr := "null string for TeePCEID" + _, err := NewTeePCEID("") + assert.EqualError(t, err, expectedErr) +} + +func TestPCEID_Valid_OK(t *testing.T) { + pceID, err := NewTeePCEID(TestPCEID) + require.NoError(t, err) + err = pceID.Valid() + require.NoError(t, err) +} + +func TestPCEID_Valid_NOK(t *testing.T) { + pceID := TeePCEID("") + expectedErr := "nil TeePCEID" + err := pceID.Valid() + assert.EqualError(t, err, expectedErr) +} diff --git a/comid/tdx-profile/teetcbcompsvn.go b/comid/tdx-profile/teetcbcompsvn.go new file mode 100644 index 00000000..ec28fe56 --- /dev/null +++ b/comid/tdx-profile/teetcbcompsvn.go @@ -0,0 +1,36 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import "fmt" + +// MaxSVNCount is the maximum SVN count in TeeTcbCompSvn +const MaxSVNCount = 16 + +type TeeTcbCompSvn [MaxSVNCount]TeeSVN + +func NewTeeTcbCompSVN(val []uint) (*TeeTcbCompSvn, error) { + if len(val) > MaxSVNCount { + return nil, fmt.Errorf("invalid length %d for TeeTcbCompSVN", len(val)) + } else if len(val) == 0 { + return nil, fmt.Errorf("no value supplied for TeeTcbCompSVN") + } + + TeeTcbCompSVN := make([]TeeSVN, MaxSVNCount) + for i, value := range val { + TeeTcbCompSVN[i] = TeeSVN(value) + } + return (*TeeTcbCompSvn)(TeeTcbCompSVN), nil +} + +// nolint:gocritic +func (o TeeTcbCompSvn) Valid() error { + if len(o) == 0 { + return fmt.Errorf("empty TeeTcbCompSVN") + } + if len(o) > MaxSVNCount { + return fmt.Errorf("invalid length: %d for TeeTcbCompSVN", len(o)) + } + return nil +} diff --git a/comid/tdx-profile/teetcbstatus.go b/comid/tdx-profile/teetcbstatus.go new file mode 100644 index 00000000..4a65c819 --- /dev/null +++ b/comid/tdx-profile/teetcbstatus.go @@ -0,0 +1,54 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// nolint:dupl +package tdx + +import "fmt" + +type TeeTcbStatus setType + +func NewTeeTcbStatus(val []any) (*TeeTcbStatus, error) { + var ts TeeTcbStatus + if len(val) == 0 { + return nil, fmt.Errorf("nil value argument") + } + + for i, v := range val { + switch t := v.(type) { + case string: + ts = append(ts, t) + default: + return nil, fmt.Errorf("invalid type: %T for tcb status at index: %d", t, i) + } + } + return &ts, nil +} + +func (o *TeeTcbStatus) AddTeeTcbStatus(val []any) error { + for i, v := range val { + switch t := v.(type) { + case string: + *o = append(*o, t) + default: + return fmt.Errorf("invalid type: %T for tcb status at index: %d", t, i) + } + } + return nil +} + +func (o TeeTcbStatus) Valid() error { + if len(o) == 0 { + return fmt.Errorf("empty tcb status") + } + + for i, v := range o { + switch t := v.(type) { + case string: + continue + default: + return fmt.Errorf("invalid type: %T for tcb status at index: %d", t, i) + } + } + return nil +} diff --git a/comid/tdx-profile/teetcbstatus_test.go b/comid/tdx-profile/teetcbstatus_test.go new file mode 100644 index 00000000..15dc398d --- /dev/null +++ b/comid/tdx-profile/teetcbstatus_test.go @@ -0,0 +1,80 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func initTcbStatus() []any { + s := make([]any, len(TestTCBStatus)) + for i := range TestTCBStatus { + s[i] = TestTCBStatus[i] + } + return s +} + +func TestTcbStatus_NewTeeTcbStatus_OK(t *testing.T) { + s := initTcbStatus() + _, err := NewTeeTcbStatus(s) + require.NoError(t, err) +} + +func TestTcbStatus_NewTeeTcbStatus_NOK(t *testing.T) { + s := make([]any, len(TestTCBStatus)) + for i := range TestTCBStatus { + s[i] = i + } + expectedErr := "invalid type: int for tcb status at index: 0" + _, err := NewTeeTcbStatus(s) + assert.EqualError(t, err, expectedErr) + var m []any + expectedErr = "nil value argument" + _, err = NewTeeTcbStatus(m) + assert.EqualError(t, err, expectedErr) +} + +func TestTcbStatus_AddTcbStatus_OK(t *testing.T) { + s := initTcbStatus() + status := TeeTcbStatus{} + err := status.AddTeeTcbStatus(s) + require.Nil(t, err) +} + +func TestTcbStatus_AddTcbStatus_NOK(t *testing.T) { + expectedErr := "invalid type: int for tcb status at index: 0" + s := make([]any, len(TestInvalidTCBStatus)) + for i := range TestInvalidTCBStatus { + s[i] = TestInvalidTCBStatus[i] + } + status := TeeTcbStatus{} + err := status.AddTeeTcbStatus(s) + assert.EqualError(t, err, expectedErr) +} + +func TestTcbStatus_Valid_OK(t *testing.T) { + s := initTcbStatus() + status, err := NewTeeTcbStatus(s) + require.Nil(t, err) + err = status.Valid() + require.Nil(t, err) +} + +func TestTcbStatus_Valid_NOK(t *testing.T) { + expectedErr := "empty tcb status" + status := TeeTcbStatus{} + err := status.Valid() + assert.EqualError(t, err, expectedErr) + expectedErr = "invalid type: int for tcb status at index: 0" + s := make([]any, len(TestInvalidTCBStatus)) + for i := range TestInvalidTCBStatus { + s[i] = TestInvalidTCBStatus[i] + } + status = TeeTcbStatus(s) + err = status.Valid() + assert.EqualError(t, err, expectedErr) +} diff --git a/comid/tdx-profile/test_vars.go b/comid/tdx-profile/test_vars.go new file mode 100644 index 00000000..4e148c61 --- /dev/null +++ b/comid/tdx-profile/test_vars.go @@ -0,0 +1,190 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +//nolint:lll +var ( + TestRegID = "https://intel.com" + TestOID = "2.16.840.1.113741.1.2.3.4.5" + TestUIntInstance = 45 + TestByteInstance = []byte{0x45, 0x46, 0x47} + TestInvalidProdID = -23 + TestUIntISVProdID = 23 + TestBytesISVProdID = []byte{0x01, 0x02, 0x03} + TestInvalidInstance = -1 + TestTeeAttributes = []byte{0x01, 0x01} + TestTeeMiscSelect = []byte{0x0B, 0x0C, 0x0D} + TestPCEID = "PCEID001" + TestCompSVN = []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + TestTCBStatus = []string{"OutOfDate", "ConfigurationNeeded", "UpToDate"} + TestInvalidTCBStatus = []int{1, 2, 3} + TestAdvisoryIDs = []string{"SA-00078", "SA-00077", "SA-00079"} + TestInvalidAdvisoryIDs = []float64{1.234, 2.567} + TestISVSVN = 10 + TestTCBEvalNum = 11 + TestTime = "2025-01-29T00:00:00Z" + TDXPCERefValTemplate = `{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B17", + "version": 0 + }, + "entities": [ + { + "name": "INTEL", + "regid": "https://intel.com", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.16.840.1.113741.1.2.3.4.6" + }, + "vendor": "Intel Corporation", + "model": "0123456789ABCDEF" + } + }, + "measurements": [ + { + "value": { + "instanceid": { + "type": "uint", + "value": 11 + }, + "tcbcompsvn": [10, 10, 2, 2, 2, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "pceid": "0000" + }, + "authorized-by": { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + } + } + ] + } + ] + } +} +` + TDXQERefValTemplate = `{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 0 + }, + "entities": [ + { + "name": "INTEL", + "regid": "https://intel.com", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.16.840.1.113741.1.2.3.4.1" + }, + "vendor": "Intel Corporation", + "model": "TDX QE TCB" + } + }, + "measurements": [ + { + "value": { + "miscselect": "wAAAAPv/AAA=", + "tcbevalnum": 11, + "mrsigner": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", + "sha-512:oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" + ], + "isvprodid": { + "type": "bytes", + "value": "AwM=" + } + }, + "authorized-by": { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + } + } + ] + } + ] + } +} +` + TDXSeamRefValJSONTemplate = `{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B20", + "version": 0 + }, + "entities": [ + { + "name": "INTEL", + "regid": "https://intel.com", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.16.840.1.113741.1.2.3.4.5" + }, + "vendor": "Intel Corporation", + "model": "TDX SEAM" + } + }, + "measurements": [ + { + "value": { + "isvprodid": { + "type": "bytes", + "value": "AwM=" + }, + "isvsvn": 10, + "attributes": "8AoL", + "tcbevalnum": 11, + "mrtee" : ["sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc="], + "mrsigner": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", + "sha-512:oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" + ] + }, + "authorized-by": { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + } + } + ] + } + ] + } +} +` +) diff --git a/comid/tdx-profile/testcases/comid_pce_refval.cbor b/comid/tdx-profile/testcases/comid_pce_refval.cbor new file mode 100644 index 0000000000000000000000000000000000000000..f0b8c0621d0c59791e1bd0422dc0fde020c5f4b9 GIT binary patch literal 382 zcmZ9Gy-vbV0EOEtF5P^C8%K@Oq~S*shW=cn(n67fmdRRf>$SAEluC(l!GSosI$}2` zcVEE=z`<8=c2gj__)RA#Cplln&Y4N6Z3oc+LnEeRG9ol2p$BCgC&VR=oh%E8LT6x) zF^sW`V;njdCbkEr8H3e^i8OWrhnGnbjSlm95+-=C@6Z5DncSS2-p!eeSy1hKR%2&h zItw;(o9s|st)NWfh{o#+;G4=BG-L!#S(-kvSy?lft!+W+xas}l^XuEk=Xd$`f#U!G zY>xS(-0bs9z&qHPyzH%z(2?4JMn}?A8P<`tE;+sGrzo{*b=p&Ju6S8&S?X_A#FnG9 zJP}nxKC!8oo(M(|*L_P%#F279`9i&|7lRAWZOC49t?^#58<{n~Hz*0IUyzgu?Uusb fCdQa2>d-$GYI@HUkV!&v-1jOKZmobC%73dLTa$R_ literal 0 HcmV?d00001 diff --git a/comid/tdx-profile/testcases/comid_qe_refval.cbor b/comid/tdx-profile/testcases/comid_qe_refval.cbor new file mode 100644 index 0000000000000000000000000000000000000000..10eafdfadeb51f0bc47b5ddd06cb98894b39a3e2 GIT binary patch literal 432 zcmZ3?xR9YjIyf=6ASYEJ*gZla&{d%*H7&I$H7_|;p(HV#sc|tws;6Iws}JK1g~E)I zl7eC@ef`Y5lGGf%&OTOA zI)$}(+Oy4HGtUP6jlJzFz3Z=bRue~r10gkp8wlSK^@7FH1AksXy|Zd{&i>f}<9A7-AX?Vp;O jYLMw!m>q8H;TsiV>>84pm+6&~Zfna0c7m&)3%aWS^`4y; literal 0 HcmV?d00001 diff --git a/comid/tdx-profile/testcases/comid_seam_refval.cbor b/comid/tdx-profile/testcases/comid_seam_refval.cbor new file mode 100644 index 0000000000000000000000000000000000000000..71e9618a33a5503e7c59b15d63f17ebfa8da3934 GIT binary patch literal 456 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km~6d;_AbAL!mIEq@NvmRmE(kcex_kO51cW;IcseV1 zyGDWqxqRKS%U#POJ-qXyJgc${oE-xrJ)vwD$G~Klz;s7fw>*QA#C*r{Amf1CBJb=- zpAyGn7t4GDQ}1A3v)rWgG(YF`f>IxYbhEI65RdGr9CPFHY*Qzfiu^G1JZ=Bf)Kr5^ h&%*3*V-Me`5M$Sn%)CsmlyqBLF0d0^{anyp1pwretXu#9 literal 0 HcmV?d00001 diff --git a/comid/tdx-profile/testcases/regen-from-src.sh b/comid/tdx-profile/testcases/regen-from-src.sh new file mode 100755 index 00000000..0ce82c2d --- /dev/null +++ b/comid/tdx-profile/testcases/regen-from-src.sh @@ -0,0 +1,21 @@ +#!/usr/bin/bash +# Copyright 2024-2025 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 +set -e + +THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +if [[ "$(type -p diag2cbor.rb)" == "" ]]; then + echo "ERROR: please install ruby-cbor-diag package" + exit 1 +fi + +for case in "$THIS_DIR"/src/*.diag; do + outfile=$(basename "${case%%.diag}").cbor + + echo "generating $outfile" + + diag2cbor.rb "$case" > "$THIS_DIR/$outfile" +done + +echo "done." diff --git a/comid/tdx-profile/testcases/src/comid_pce_refval.diag b/comid/tdx-profile/testcases/src/comid_pce_refval.diag new file mode 100644 index 00000000..e40b8bd7 --- /dev/null +++ b/comid/tdx-profile/testcases/src/comid_pce_refval.diag @@ -0,0 +1,34 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : "Sample Provisioning Certification Enclave reference tag" + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "INTEL", + / comid.reg-id / 1 : 32("https://intel.com"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111( + h'6086480186F84D0102030405' / 2.16.840.1.113741.1.2.3.4.5 - / + ), + / comid.vendor / 1 : "Intel Corporation", + / comid.model / 2 : "TDX PCE TCB" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.instanceid / -77 : h'00112233445566778899aabbccddeeff', + / tcb-comp-svn / -125 : [ 10, 10, 2, 2, 2, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + / pceid / -80 : "0000" + }, + / authorized-by / 2: 554("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----") + } + ] + ] ] + } +} \ No newline at end of file diff --git a/comid/tdx-profile/testcases/src/comid_qe_refval.diag b/comid/tdx-profile/testcases/src/comid_qe_refval.diag new file mode 100644 index 00000000..bf060279 --- /dev/null +++ b/comid/tdx-profile/testcases/src/comid_qe_refval.diag @@ -0,0 +1,44 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : "Sample SGX QE reference tag" + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "INTEL", + / comid.reg-id / 1 : 32("https://intel.com"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111( + h'6086480186F84D0102030401' / 2.16.840.1.113741.1.2.3.4.1 - / + ), + / comid.vendor / 1 : "Intel Corporation", + / comid.model / 2 : "SGX QE TCB" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.miscselect / -81 :h'A0B0C0D000000000', + / comid.isvprodid / -85 : 1, + / comid.mrsigner / -84 : [ + [ + / alg-id / 1, / sha256 / + / digest / h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A' + ], + [ + / alg-id / 8, / sha384 / + / digest / h'a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a' + ] + ], + /comid.tcbevalnum / -86 : 11 + }, + / authorized-by / 2: 554("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----") + } + ] + ] ] + } +} \ No newline at end of file diff --git a/comid/tdx-profile/testcases/src/comid_seam_refval.diag b/comid/tdx-profile/testcases/src/comid_seam_refval.diag new file mode 100644 index 00000000..32cfe5cc --- /dev/null +++ b/comid/tdx-profile/testcases/src/comid_seam_refval.diag @@ -0,0 +1,51 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "INTEL", + / comid.reg-id / 1 : 32("https://intel.com"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111( + h'6086480186F84D0102030403' + ), + / comid.vendor / 1 : "Intel Corporation", + / comid.model / 2 : "TDX SEAM" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.attributes / -82 :[ 1, 2], + / comid.isvprodid / -85 : h'ABCD', + / comid.isvsvn / -73 : 6, + / comid.mrtee / -83 : [ + [ + / alg-id / 1, / sha256 / + / digest / h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A' + ] + ], + / comid.mrsigner / -84 : [ + [ + / alg-id / 1, / sha256 / + / digest / h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A' + ], + [ + / alg-id / 8, / sha384 / + / digest / h'a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a' + ] + ], + /comid.tcbevalnum / -86 : 11 + }, + 2: 554("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----") + } + ] + ] ] + } +} \ No newline at end of file diff --git a/comid/tdx-profile/types.go b/comid/tdx-profile/types.go new file mode 100644 index 00000000..faddb11d --- /dev/null +++ b/comid/tdx-profile/types.go @@ -0,0 +1,14 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import "github.com/veraison/corim/comid" + +type setType []any + +type maskType []byte +type TeeSVN uint +type TeeDigest comid.Digests + +type TeeTcbEvalNum uint diff --git a/comid/test_vars.go b/comid/test_vars.go index 5474da06..49de3ba6 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -37,6 +37,7 @@ var ( TestMKey uint64 = 700 TestCCALabel = "cca-platform-config" + //nolint:gosec TestECPrivKey = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEICAm3+mCCDTMuzKqfZso9NT8ur9U9GjuUQ/lNEJvwRFMoAoGCCqGSM49 AwEHoUQDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8BlLT4MFHOaO+ICTtIvrEeEpr/ diff --git a/comid/triples.go b/comid/triples.go index 7348dace..a937b7ef 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -64,7 +64,7 @@ func (o *Triples) RegisterExtensions(exts extensions.Map) error { return nil } -// GetExtensions returns pervisouosly registered extension +// GetExtensions returns previously registered extension func (o *Triples) GetExtensions() extensions.IMapValue { return o.Extensions.IMapValue } From 5bd3aebd180f717f9765158a8f5890acb499d806 Mon Sep 17 00:00:00 2001 From: Jagannathan Raman Date: Mon, 3 Feb 2025 12:10:20 +0000 Subject: [PATCH 071/110] fix(Version): refactor Version type into separate file - refactor Version type into a separate file - add equality and comparison methods Signed-off-by: Jagannathan Raman --- comid/measurement.go | 36 +--------------------------- comid/version.go | 55 +++++++++++++++++++++++++++++++++++++++++++ comid/version_test.go | 49 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 35 deletions(-) create mode 100644 comid/version.go create mode 100644 comid/version_test.go diff --git a/comid/measurement.go b/comid/measurement.go index 08a54419..98549747 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -13,7 +13,6 @@ import ( "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" "github.com/veraison/eat" - "github.com/veraison/swid" ) const MaxUint64 = ^uint64(0) @@ -489,39 +488,6 @@ func (o Mval) Valid() error { return o.Extensions.validMval(&o) } -// Version stores a version-map with JSON and CBOR serializations. -type Version struct { - Version string `cbor:"0,keyasint" json:"value"` - Scheme swid.VersionScheme `cbor:"1,keyasint" json:"scheme"` -} - -func NewVersion() *Version { - return &Version{} -} - -func (o *Version) SetVersion(v string) *Version { - if o != nil { - o.Version = v - } - return o -} - -func (o *Version) SetScheme(v int64) *Version { - if o != nil { - if o.Scheme.SetCode(v) != nil { - return nil - } - } - return o -} - -func (o Version) Valid() error { - if o.Version == "" { - return fmt.Errorf("empty version") - } - return nil -} - // Measurement stores a measurement-map with CBOR and JSON serializations. type Measurement struct { Key *Mkey `cbor:"0,keyasint,omitempty" json:"key,omitempty"` diff --git a/comid/version.go b/comid/version.go new file mode 100644 index 00000000..e1dbe296 --- /dev/null +++ b/comid/version.go @@ -0,0 +1,55 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "fmt" + + "github.com/veraison/swid" +) + +// Version stores a version-map with JSON and CBOR serializations. +type Version struct { + Version string `cbor:"0,keyasint" json:"value"` + Scheme swid.VersionScheme `cbor:"1,keyasint" json:"scheme"` +} + +func NewVersion() *Version { + return &Version{} +} + +func (o *Version) SetVersion(v string) *Version { + if o != nil { + o.Version = v + } + return o +} + +func (o *Version) SetScheme(v int64) *Version { + if o != nil { + if o.Scheme.SetCode(v) != nil { + return nil + } + } + return o +} + +func (o Version) Valid() error { + if o.Version == "" { + return fmt.Errorf("empty version") + } + return nil +} + +func (o Version) Equal(r Version) bool { + if o.Scheme != r.Scheme || o.Version != r.Version { + return false + } + + return true +} + +func (o Version) CompareAgainstReference(r Version) bool { + return o.Equal(r) +} diff --git a/comid/version_test.go b/comid/version_test.go new file mode 100644 index 00000000..605d7774 --- /dev/null +++ b/comid/version_test.go @@ -0,0 +1,49 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/veraison/swid" +) + +func TestVersion_Valid_OK(t *testing.T) { + v := NewVersion() + + v.SetVersion("1.55.22") + v.SetScheme(swid.VersionSchemeSemVer) + + assert.Nil(t, v.Valid()) +} + +func TestVersion_Valid_NOK(t *testing.T) { + v := NewVersion() + + assert.EqualError(t, v.Valid(), "empty version") +} + +func TestVersion_Equal_True(t *testing.T) { + claim := NewVersion() + claim.SetVersion("1.55.22") + claim.SetScheme(swid.VersionSchemeSemVer) + + ref := NewVersion() + ref.SetVersion("1.55.22") + ref.SetScheme(swid.VersionSchemeSemVer) + + assert.True(t, claim.Equal(*ref)) +} + +func TestVersion_Equal_False(t *testing.T) { + claim := NewVersion() + claim.SetVersion("1.55.22") + claim.SetScheme(swid.VersionSchemeSemVer) + + ref := NewVersion() + ref.SetVersion("1.55.40") + ref.SetScheme(swid.VersionSchemeSemVer) + + assert.False(t, claim.Equal(*ref)) +} From 1db3a36b3f4627c8429a91572d79e084c8cd8994 Mon Sep 17 00:00:00 2001 From: Jagannathan Raman Date: Mon, 3 Feb 2025 12:40:15 +0000 Subject: [PATCH 072/110] svn: add comparison and equality methods - Add methods to test the equality of TaggedSVN & TaggedMinSVN types - Add comparison methods for TaggedSVN as outlined in the spec Signed-off-by: Sergei Trofimov Signed-off-by: Jagannathan Raman --- comid/svn.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++- comid/svn_test.go | 44 ++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/comid/svn.go b/comid/svn.go index 692fad18..fdf81feb 100644 --- a/comid/svn.go +++ b/comid/svn.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -6,6 +6,7 @@ package comid import ( "encoding/json" "fmt" + "log" "strconv" "github.com/veraison/corim/encoding" @@ -143,6 +144,30 @@ func (o TaggedSVN) Valid() error { return nil } +func (o TaggedSVN) Equal(r TaggedSVN) bool { + ret, err := compare(o, r) + if err != nil { + log.Printf("TaggedSVN:Equal: %v", err) + return false + } + + return ret == 0 +} + +func (o TaggedSVN) CompareAgainstRefSVN(r TaggedSVN) bool { + return o.Equal(r) +} + +func (o TaggedSVN) CompareAgainstRefMinSVN(r TaggedMinSVN) bool { + ret, err := compare(o, r) + if err != nil { + log.Printf("TaggedSVN:CompareAgainstRefMinSVN: %v", err) + return false + } + + return ret >= 0 +} + type TaggedMinSVN uint64 func NewTaggedMinSVN(val any) (*SVN, error) { @@ -182,6 +207,42 @@ func (o TaggedMinSVN) Valid() error { return nil } +func (o TaggedMinSVN) Equal(r TaggedMinSVN) bool { + ret, err := compare(o, r) + if err != nil { + log.Printf("TaggedMinSVN:Equal: %v", err) + return false + } + + return ret == 0 +} + +// Compare helper function to compare two SVNs, object and reference +// +// returns: +// 0 if they are equal; no error +// 1 if object is newer than the reference; no error +// -1 if object is older than reference or if the function encounters an error +func compare(o any, r any) (int, error) { + obj, err := convertToSVNUint64(o) + if err != nil { + return -1, fmt.Errorf("object Error: %v", err) + } + + ref, err := convertToSVNUint64(r) + if err != nil { + return -1, fmt.Errorf("reference Error: %v", err) + } + + if obj < ref { + return -1, nil + } else if obj > ref { + return 1, nil + } + + return 0, nil +} + // convertToSVNUint64 converts various SVN types to uint64. func convertToSVNUint64(val any) (uint64, error) { switch t := val.(type) { diff --git a/comid/svn_test.go b/comid/svn_test.go index b9d29ad6..b8876720 100644 --- a/comid/svn_test.go +++ b/comid/svn_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -182,3 +182,45 @@ func Test_RegisterSVNType(t *testing.T) { require.NoError(t, err) } + +func Test_TaggedSVN_Equal_True(t *testing.T) { + claim := TaggedSVN(7) + ref := TaggedSVN(7) + + assert.True(t, claim.Equal(ref)) +} + +func Test_TaggedSVN_Equal_False(t *testing.T) { + claim := TaggedSVN(7) + ref := TaggedSVN(8) + + assert.False(t, claim.Equal(ref)) +} + +func Test_TaggedSVN_CompareAgainstRefMinSVN_True(t *testing.T) { + claim := TaggedSVN(8) + ref := TaggedMinSVN(7) + + assert.True(t, claim.CompareAgainstRefMinSVN(ref)) +} + +func Test_TaggedSVN_CompareAgainstRefMinSVN_False(t *testing.T) { + claim := TaggedSVN(7) + ref := TaggedMinSVN(8) + + assert.False(t, claim.CompareAgainstRefMinSVN(ref)) +} + +func Test_TaggedMinSVN_Equal_True(t *testing.T) { + claim := TaggedMinSVN(8) + ref := TaggedMinSVN(8) + + assert.True(t, claim.Equal(ref)) +} + +func Test_TaggedMinSVN_Equal_False(t *testing.T) { + claim := TaggedMinSVN(7) + ref := TaggedMinSVN(8) + + assert.False(t, claim.Equal(ref)) +} From 6e88710d87effa7c04c0b8ddeb19c58fdbd75811 Mon Sep 17 00:00:00 2001 From: Jagannathan Raman Date: Mon, 3 Feb 2025 12:59:26 +0000 Subject: [PATCH 073/110] Digests: add equality and comparison methods - add a method to test if two digests are equal - add a method to compare a digests object against reference, as outlined in the CoRIM spec Signed-off-by: Jagannathan Raman --- comid/digests.go | 83 +++++++++++++++++++++++++++++++++++++++++- comid/digests_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 165 insertions(+), 2 deletions(-) diff --git a/comid/digests.go b/comid/digests.go index f6f4f8b7..e74813de 100644 --- a/comid/digests.go +++ b/comid/digests.go @@ -1,9 +1,10 @@ -// Copyright 2021 Contributors to the Veraison project. +// Copyright 2021-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid import ( + "bytes" "fmt" "github.com/veraison/swid" @@ -41,6 +42,86 @@ func (o Digests) Valid() error { return nil } +// Equal confirms if the Digests object is the same as another +// +// Two digests are considered to be equal if they meet the following criteria: +// - They contain the same number of elements +// - All the elements that use the same algorithm have the same value, +// though the elements could be in any order +func (o Digests) Equal(r Digests) bool { + om := make(map[uint64][][]byte) + for _, oe := range o { + vs, ok := om[oe.HashAlgID] + if ok { + om[oe.HashAlgID] = append(vs, oe.HashValue) + } else { + om[oe.HashAlgID] = [][]byte{oe.HashValue} + } + } + +outer: + for _, re := range r { + ovs, ok := om[re.HashAlgID] + if !ok { + return false + } + + for _, ov := range ovs { + if bytes.Equal(ov, re.HashValue) { + continue outer + } + } + + return false + } + + return true +} + +// CompareAgainstReference checks if digests object matches with a reference +// +// See the following CoRIM spec for rules to compare +// digests against a reference: +// https://ietf-rats-wg.github.io/draft-ietf-rats-corim/draft-ietf-rats-corim.html#section-8.5.6.1.3 +func (o Digests) CompareAgainstReference(r Digests) bool { + result := false + + if len(r) == 0 { + return false + } + + // Insert the reference values into a map + ref := make(map[uint64][]byte) + for _, digest := range r { + val, ok := ref[digest.HashAlgID] + if ok && !bytes.Equal(digest.HashValue, val) { + // If two entries with the same hashing algorithm have different + // values, that's an automatic false. + return false + } + + ref[digest.HashAlgID] = digest.HashValue + } + + // Check the object against the reference value map + for _, digest := range o { + val, ok := ref[digest.HashAlgID] + if !ok { + continue + } + + if !bytes.Equal(digest.HashValue, val) { + // All hash values must be equal if a claim has the same + // digest represented using multiple algorithms. + return false + } + + result = true + } + + return result +} + func NewHashEntry(algID uint64, value []byte) *swid.HashEntry { var he swid.HashEntry diff --git a/comid/digests_test.go b/comid/digests_test.go index 663a064d..71d4f67b 100644 --- a/comid/digests_test.go +++ b/comid/digests_test.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -147,3 +147,85 @@ func TestDigests_UnmarshalCBOR(t *testing.T) { assert.Equal(t, swid.Sha256_64, actual[1].HashAlgID) assert.Equal(t, MustHexDecode(t, "e45b72f5c0c0b572"), actual[1].HashValue) } + +func TestDigests_Equal_True(t *testing.T) { + ref := NewDigests(). + AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + + claim := NewDigests(). + AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). + AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")) + + assert.True(t, claim.Equal(*ref)) +} + +func TestDigests_Equal_False_Length(t *testing.T) { + ref := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + + claim := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")) + + assert.False(t, claim.Equal(*ref)) +} + +func TestDigests_Equal_False_Mismatch(t *testing.T) { + ref := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + + claim := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha256_64, MustHexDecode(t, "a26c83e2d0c0b572")) + + assert.False(t, claim.Equal(*ref)) +} + +func TestDigests_Compare_True(t *testing.T) { + ref := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha384, MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) + + claim := NewDigests(). + AddDigest(swid.Sha384, MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) + + assert.True(t, claim.CompareAgainstReference(*ref)) +} + +func TestDigests_Compare_False(t *testing.T) { + ref := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + + claim := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "f39a61fe")) + + assert.False(t, claim.CompareAgainstReference(*ref)) +} + +func TestDigests_Compare_False_DuplicateIDs(t *testing.T) { + ref := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha256_32, MustHexDecode(t, "f34a51de")) + + claim := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")) + + assert.False(t, claim.CompareAgainstReference(*ref)) +} + +func TestDigests_Compare_False_PartialMatch(t *testing.T) { + ref := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + + claim := NewDigests(). + AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(swid.Sha256_64, MustHexDecode(t, "f39c2473a0c0f592")) + + assert.False(t, claim.CompareAgainstReference(*ref)) +} From ba74debbebdc683938070a2456b417b7856da693 Mon Sep 17 00:00:00 2001 From: Jagannathan Raman Date: Thu, 6 Feb 2025 15:04:25 -0500 Subject: [PATCH 074/110] FlagsMap: add equality and comparison methods Add equality and comparison methods for FlagsMap object Signed-off-by: Jagannathan Raman --- comid/flagsmap.go | 11 ++++++++++- comid/flagsmap_test.go | 27 ++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/comid/flagsmap.go b/comid/flagsmap.go index 5a68233f..bdcf298c 100644 --- a/comid/flagsmap.go +++ b/comid/flagsmap.go @@ -1,10 +1,11 @@ -// Copyright 2023-2024 Contributors to the Veraison project. +// Copyright 2023-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid import ( "fmt" + "reflect" "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" @@ -179,6 +180,14 @@ func (o *FlagsMap) Get(flag Flag) *bool { } } +func (o FlagsMap) Equal(r FlagsMap) bool { //nolint:gocritic + return reflect.DeepEqual(o, r) +} + +func (o FlagsMap) CompareAgainstReference(r FlagsMap) bool { //nolint:gocritic + return o.Equal(r) +} + // RegisterExtensions registers a struct as a collections of extensions func (o *FlagsMap) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { diff --git a/comid/flagsmap_test.go b/comid/flagsmap_test.go index 665552b1..fa506a5a 100644 --- a/comid/flagsmap_test.go +++ b/comid/flagsmap_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -41,3 +41,28 @@ func Test_FlagsMap(t *testing.T) { assert.False(t, fm.AnySet()) assert.Equal(t, (*bool)(nil), fm.Get(Flag(-1))) } + +func Test_FlagsMap_Equal_True(t *testing.T) { + claim := NewFlagsMap() + ref := NewFlagsMap() + + claim.SetTrue(FlagIsSecure) + claim.SetTrue(FlagIsRuntimeMeasured) + + ref.SetTrue(FlagIsSecure) + ref.SetTrue(FlagIsRuntimeMeasured) + + assert.True(t, claim.Equal(*ref)) +} + +func Test_FlagsMap_Equal_False(t *testing.T) { + claim := NewFlagsMap() + ref := NewFlagsMap() + + claim.SetTrue(FlagIsSecure) + claim.SetTrue(FlagIsRuntimeMeasured) + + ref.SetTrue(FlagIsSecure) + + assert.False(t, claim.Equal(*ref)) +} From ea261b48e6b08ca266ddeb695d3117ca65b79bb7 Mon Sep 17 00:00:00 2001 From: Jagannathan Raman Date: Thu, 6 Feb 2025 15:15:08 -0500 Subject: [PATCH 075/110] RawValue: add equality and comparison methods for RawValue - add a method to test if a RawValue type equals another one - add a comparison method to check against a reference Signed-off-by: Jagannathan Raman --- comid/rawvalue.go | 35 ++++++++++++++++++++++++++++++++++- comid/rawvalue_test.go | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/comid/rawvalue.go b/comid/rawvalue.go index fd3ceae9..28592224 100644 --- a/comid/rawvalue.go +++ b/comid/rawvalue.go @@ -1,11 +1,14 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid import ( + "bytes" "encoding/json" "fmt" + "log" + "reflect" ) // RawValue models a $raw-value-type-choice. For now, the only available type is bytes. @@ -41,6 +44,36 @@ func (o RawValue) GetBytes() ([]byte, error) { } } +func (o RawValue) Equal(r RawValue) bool { + return reflect.DeepEqual(o, r) +} + +func (o RawValue) CompareAgainstReference(ref []byte, mask *[]byte) bool { + claim, err := o.GetBytes() + if err != nil { + log.Printf("RawValue:CompareAgainstReference: Error: %v", err) + return false + } + + if mask != nil { + if len(claim) != len(ref) { + return false + } + + if len(*mask) != len(claim) { + log.Printf("RawValue:CompareAgainstReference: Error: mask length") + return false + } + + for i := range *mask { + claim[i] = (*mask)[i] & claim[i] + ref[i] = (*mask)[i] & ref[i] + } + } + + return bytes.Equal(claim, ref) +} + func (o RawValue) MarshalCBOR() ([]byte, error) { return em.Marshal(o.val) } diff --git a/comid/rawvalue_test.go b/comid/rawvalue_test.go index d28d6aad..a0cf3617 100644 --- a/comid/rawvalue_test.go +++ b/comid/rawvalue_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -56,3 +56,38 @@ func TestRawValue_Marshal_UnMarshal_CBOR_ok(t *testing.T) { assert.NoError(t, err) assert.Equal(t, *rv, sv) } + +func TestRawValue_Equal_True(t *testing.T) { + claim := RawValue{} + claim.SetBytes([]byte{0x01, 0x02, 0x03}) + ref := RawValue{} + ref.SetBytes([]byte{0x01, 0x02, 0x03}) + + assert.True(t, claim.Equal(ref)) +} + +func TestRawValue_Equal_False(t *testing.T) { + claim := RawValue{} + claim.SetBytes([]byte{0x01, 0x02, 0x03}) + ref := RawValue{} + ref.SetBytes([]byte{0x01, 0x02, 0x04}) + + assert.False(t, claim.Equal(ref)) +} + +func TestRawValue_Compare_True(t *testing.T) { + claim := RawValue{} + claim.SetBytes([]byte{0x01, 0x02, 0x03}) + ref := []byte{0x01, 0x00, 0x03} + mask := []byte{0xff, 0x00, 0xff} + + assert.True(t, claim.CompareAgainstReference(ref, &mask)) +} + +func TestRawValue_Compare_False(t *testing.T) { + claim := RawValue{} + claim.SetBytes([]byte{0x01, 0x02, 0x03}) + ref := []byte{0x04, 0x05, 0x06} + + assert.False(t, claim.CompareAgainstReference(ref, nil)) +} From ed3846782f5fd257feb290ebdc4a86c11e68f80e Mon Sep 17 00:00:00 2001 From: Jagannathan Raman Date: Mon, 10 Feb 2025 16:35:10 -0500 Subject: [PATCH 076/110] MACaddr: add equality and comparison methods Add equality and comparison methods to test against reference Signed-off-by: Jagannathan Raman --- comid/macaddr.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/comid/macaddr.go b/comid/macaddr.go index 8733c4f5..fa5dbd6e 100644 --- a/comid/macaddr.go +++ b/comid/macaddr.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "net" + "reflect" ) // MACaddr is an HW address (e.g., IEEE 802 MAC-48, EUI-48, EUI-64) @@ -16,6 +17,14 @@ import ( // we need to create an alias type with a custom decoder. type MACaddr net.HardwareAddr +func (o MACaddr) Equal(r MACaddr) bool { + return reflect.DeepEqual(o, r) +} + +func (o MACaddr) CompareAgainstReference(r MACaddr) bool { + return o.Equal(r) +} + // UnmarshalJSON deserialize a MAC address in textual form into the MACaddr // target, e.g.: // From 3736981cee0d60806d331277286b7dcf2c04a6a6 Mon Sep 17 00:00:00 2001 From: Jagannathan Raman Date: Mon, 10 Feb 2025 16:45:51 -0500 Subject: [PATCH 077/110] IntegrityRegisters: add equality and comparison methods - Add a method to test if an IntegrityRegisters object equals another - Add a method to compare IntegrityRegisters object against a reference per CoRIM spec Signed-off-by: Jagannathan Raman --- comid/integrityregisters.go | 32 +++++++++++- comid/integrityregisters_test.go | 89 +++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/comid/integrityregisters.go b/comid/integrityregisters.go index fb149305..ae713b0e 100644 --- a/comid/integrityregisters.go +++ b/comid/integrityregisters.go @@ -1,10 +1,11 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid import ( "encoding/json" "fmt" + "reflect" "strconv" "github.com/veraison/swid" @@ -136,3 +137,32 @@ func (i *IntegrityRegisters) UnmarshalJSON(data []byte) error { } return nil } + +func (i IntegrityRegisters) Equal(r IntegrityRegisters) bool { + return reflect.DeepEqual(i, r) +} + +// CompareAgainstReference checks if IntegrityRegisters object matches with a reference +// +// See the following CoRIM spec for rules to compare +// IntegrityRegisters against a reference: +// https://ietf-rats-wg.github.io/draft-ietf-rats-corim/draft-ietf-rats-corim.html#name-comparison-for-integrity-re +func (i IntegrityRegisters) CompareAgainstReference(r IntegrityRegisters) bool { + result := false + + for refIndex, refDigests := range r.IndexMap { + claimDigests, ok := i.IndexMap[refIndex] + if !ok { + return false + } + + ref := refDigests + if !claimDigests.CompareAgainstReference(ref) { + return false + } + + result = true + } + + return result +} diff --git a/comid/integrityregisters_test.go b/comid/integrityregisters_test.go index 079956e9..204651d5 100644 --- a/comid/integrityregisters_test.go +++ b/comid/integrityregisters_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -300,3 +300,90 @@ func TestIntegrityRegisters_UnmarshalJSON_NOK(t *testing.T) { }) } } + +func TestIntegrityRegisters_Equal_True(t *testing.T) { + claim := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + + ref := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + + assert.True(t, claim.Equal(ref)) +} + +func TestIntegrityRegisters_Equal_False(t *testing.T) { + claim := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + + ref := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + + assert.False(t, claim.Equal(ref)) +} + +func TestIntegrityRegisters_Compare_True(t *testing.T) { + claim := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, + uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "9aca8354b65a9b4815cf471a6fe9ca9629389691c4183831e63c37a744b2d8ec")}}, + }} + + ref := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, + }} + + assert.True(t, claim.CompareAgainstReference(ref)) +} + +func TestIntegrityRegisters_Compare_False(t *testing.T) { + claim := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, + uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "9aca8354b65a9b4815cf471a6fe9ca9629389691c4183831e63c37a744b2d8ec")}}, + }} + + ref := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + + assert.False(t, claim.CompareAgainstReference(ref)) +} + +func TestIntegrityRegisters_Compare_False_MissingEntry(t *testing.T) { + claim := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, + uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, + uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, + }} + + ref := IntegrityRegisters{map[IRegisterIndex]Digests{ + uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + }} + + assert.False(t, claim.CompareAgainstReference(ref)) +} From 9f597e6fd7bb989fa1151e7cee7da72ea846376a Mon Sep 17 00:00:00 2001 From: Jagannathan Raman Date: Thu, 27 Feb 2025 15:49:12 -0500 Subject: [PATCH 078/110] fix(digests/rawvalue/svn): method header comments fix/add method header comments Signed-off-by: Jagannathan Raman --- comid/digests.go | 2 +- comid/rawvalue.go | 5 +++++ comid/svn.go | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/comid/digests.go b/comid/digests.go index e74813de..c07d85cd 100644 --- a/comid/digests.go +++ b/comid/digests.go @@ -42,7 +42,7 @@ func (o Digests) Valid() error { return nil } -// Equal confirms if the Digests object is the same as another +// Equal confirms if the Digests instances are equal // // Two digests are considered to be equal if they meet the following criteria: // - They contain the same number of elements diff --git a/comid/rawvalue.go b/comid/rawvalue.go index 28592224..52bef939 100644 --- a/comid/rawvalue.go +++ b/comid/rawvalue.go @@ -44,10 +44,15 @@ func (o RawValue) GetBytes() ([]byte, error) { } } +// Equal confirms if the RawValue instances are equal func (o RawValue) Equal(r RawValue) bool { return reflect.DeepEqual(o, r) } +// CompareAgainstReference checks if a RawValue object matches with a reference +// +// See section-8.9.6.1.4 in the IETF CoRIM spec for the rules to compare a +// RawValue object against a reference. func (o RawValue) CompareAgainstReference(ref []byte, mask *[]byte) bool { claim, err := o.GetBytes() if err != nil { diff --git a/comid/svn.go b/comid/svn.go index fdf81feb..6f0bbf3b 100644 --- a/comid/svn.go +++ b/comid/svn.go @@ -217,7 +217,7 @@ func (o TaggedMinSVN) Equal(r TaggedMinSVN) bool { return ret == 0 } -// Compare helper function to compare two SVNs, object and reference +// Compare is a helper function to compare two SVNs, object and reference // // returns: // 0 if they are equal; no error From 52932f097f270406e7d1c81fe224b8124e9ee35a Mon Sep 17 00:00:00 2001 From: Jag Raman Date: Thu, 27 Feb 2025 16:20:51 -0500 Subject: [PATCH 079/110] Update comid/svn.go Co-authored-by: Thomas Fossati --- comid/svn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comid/svn.go b/comid/svn.go index 6f0bbf3b..38654dd7 100644 --- a/comid/svn.go +++ b/comid/svn.go @@ -217,7 +217,7 @@ func (o TaggedMinSVN) Equal(r TaggedMinSVN) bool { return ret == 0 } -// Compare is a helper function to compare two SVNs, object and reference +// compare is a helper function to compare two SVNs, object and reference // // returns: // 0 if they are equal; no error From 31ce94f22b96b29f9e0fe42ff4d6cc6b77758942 Mon Sep 17 00:00:00 2001 From: "Akhilesh Kr." <2827daya@gmail.com> Date: Wed, 5 Mar 2025 16:31:45 +0530 Subject: [PATCH 080/110] feat: Add methods to include signing cert and chain as x5chain (#168) Add methods to include the signing cert and its chain. Modify the Sign method to produce an x5chain in the COSE protected header if any such cert(s) have been configured. Signed-off-by: Akhilesh Kr. Yadav --- corim/signedcorim.go | 57 +++++++++++++++++++-- corim/signedcorim_test.go | 103 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 5 deletions(-) diff --git a/corim/signedcorim.go b/corim/signedcorim.go index abe12364..ac579bd0 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto" "crypto/rand" + "crypto/x509" "errors" "fmt" "strings" @@ -24,9 +25,11 @@ var ( // SignedCorim encodes a signed-corim message (i.e., a COSE Sign1 wrapped CoRIM) // with signature and verification methods type SignedCorim struct { - UnsignedCorim UnsignedCorim - Meta Meta - message *cose.Sign1Message + UnsignedCorim UnsignedCorim + Meta Meta + message *cose.Sign1Message + signingCert *x509.Certificate + intermediateCerts []*x509.Certificate } // NewSignedCorim instantiates an empty SignedCorim @@ -126,9 +129,45 @@ func (o *SignedCorim) FromCOSE(buf []byte) error { return nil } +// AddSigningCert adds a DER-encoded X.509 certificate to be included in the +// protected header of the COSE Sign1 message as the leaf certificate in X5Chain. +func (o *SignedCorim) AddSigningCert(der []byte) error { + if der == nil { + return errors.New("nil signing cert") + } + + cert, err := x509.ParseCertificate(der) + if err != nil { + return fmt.Errorf("invalid signing certificate: %w", err) + } + + o.signingCert = cert + return nil +} + +// AddIntermediateCerts adds DER-encoded X.509 certificates to be included in the protected +// header of the COSE Sign1 message as part of the X5Chain. +// The certificates must be concatenated with no intermediate padding, as per X.509 convention. +func (o *SignedCorim) AddIntermediateCerts(der []byte) error { + if len(der) == 0 { + return errors.New("nil or empty intermediate certs") + } + + certs, err := x509.ParseCertificates(der) + if err != nil { + return fmt.Errorf("invalid intermediate certificates: %w", err) + } + + if len(certs) == 0 { + return errors.New("no certificates found in intermediate cert data") + } + + o.intermediateCerts = certs + return nil +} + // Sign returns the serialized signed-corim, signed by the supplied cose Signer. -// The target SignedCorim must have its UnsignedCorim field correctly -// populated. +// The target SignedCorim must have its UnsignedCorim field correctly populated. func (o *SignedCorim) Sign(signer cose.Signer) ([]byte, error) { if signer == nil { return nil, errors.New("nil signer") @@ -161,6 +200,14 @@ func (o *SignedCorim) Sign(signer cose.Signer) ([]byte, error) { o.message.Headers.Protected[cose.HeaderLabelContentType] = ContentType o.message.Headers.Protected[HeaderLabelCorimMeta] = metaCBOR + if o.signingCert != nil { + certChain := [][]byte{o.signingCert.Raw} + for _, cert := range o.intermediateCerts { + certChain = append(certChain, cert.Raw) + } + o.message.Headers.Protected[cose.HeaderLabelX5Chain] = certChain + } + err = o.message.Sign(rand.Reader, NoExternalData, signer) if err != nil { return nil, fmt.Errorf("COSE Sign1 signature failed: %w", err) diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 2e286489..19c737e5 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -4,7 +4,10 @@ package corim import ( + "bytes" "fmt" + "os" + "path/filepath" "testing" "time" @@ -530,3 +533,103 @@ func TestSignedCorim_extensions(t *testing.T) { err = s.RegisterExtensions(badMap) assert.EqualError(t, err, `unexpected extension point: "test"`) } + +func TestSignedCorim_AddSigningCert(t *testing.T) { + certPath := filepath.Join("..", "misc", "endEntity.der") + validCert, err := os.ReadFile(certPath) + require.NoError(t, err, "Failed to read test certificate.") + + tests := []struct { + name string + certDer []byte + wantErr bool + errMsg string + }{ + // Positive test - valid certificate + {"valid cert", validCert, false, ""}, + // Negative test - nil input + {"nil cert", nil, true, "nil signing cert"}, + // Negative test - invalid certificate data + {"invalid cert", []byte("not a certificate"), true, "invalid signing certificate: x509: malformed certificate"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := NewSignedCorim() + err := s.AddSigningCert(tt.certDer) + + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, s.signingCert) + assert.EqualError(t, err, tt.errMsg) + } else { + assert.NoError(t, err) + assert.NotNil(t, s.signingCert) + } + }) + } +} + +func concatFiles(files ...string) ([]byte, error) { + var buf bytes.Buffer + + for _, file := range files { + b, err := os.ReadFile(file) + if err != nil { + return nil, err + } + buf.Write(b) + } + + return buf.Bytes(), nil +} + +func TestSignedCorim_AddIntermediateCerts(t *testing.T) { + certPath := filepath.Join("..", "misc", "intermediateCA.der") + validCert, err := os.ReadFile(certPath) + require.NoError(t, err, "Failed to read test certificate") + + // Add concatenated certificates for testing certificate chains + validCertChain, err := concatFiles( + filepath.Join("..", "misc", "intermediateCA.der"), + filepath.Join("..", "misc", "rootCA.der")) + require.NoError(t, err, "Failed to read certificate chain") + + tests := []struct { + name string + certDer []byte + wantErr bool + errMsg string + certCount int + }{ + // Positive test - valid certificate + {"valid cert", validCert, false, "", 1}, + // Positive test - certificate chain + {"cert chain", validCertChain, false, "", 2}, + // Negative test - empty input + {"empty cert", []byte{}, true, "nil or empty intermediate certs", 0}, + // Negative test - invalid certificate data + {"invalid cert", []byte("not a certificate"), true, "invalid intermediate certificates: x509: malformed certificate", 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := NewSignedCorim() + err := s.AddIntermediateCerts(tt.certDer) + + if tt.wantErr { + assert.Error(t, err) + assert.Empty(t, s.intermediateCerts) + assert.EqualError(t, err, tt.errMsg) + } else { + assert.NoError(t, err) + assert.NotEmpty(t, s.intermediateCerts) + + if tt.certCount > 0 { + assert.Equal(t, tt.certCount, len(s.intermediateCerts), + "Should have %d certificates", tt.certCount) + } + } + }) + } +} From 1e9b8201acf21e3460875cd03e2f6618c37136d9 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Wed, 5 Mar 2025 16:00:07 +0100 Subject: [PATCH 081/110] feat(corim): decode optional x5chain Signed-off-by: Thomas Fossati --- corim/signedcorim.go | 54 +++++++++++++++++++++++++++++----- corim/signedcorim_test.go | 61 +++++++++++++++++++++++++++++++++++---- 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/corim/signedcorim.go b/corim/signedcorim.go index ac579bd0..fa9f20ff 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -28,8 +28,8 @@ type SignedCorim struct { UnsignedCorim UnsignedCorim Meta Meta message *cose.Sign1Message - signingCert *x509.Certificate - intermediateCerts []*x509.Certificate + SigningCert *x509.Certificate + IntermediateCerts []*x509.Certificate } // NewSignedCorim instantiates an empty SignedCorim @@ -94,6 +94,46 @@ func (o *SignedCorim) processHdrs() error { o.Meta = meta + if v, ok := hdr.Protected[cose.HeaderLabelX5Chain]; ok { + arr, ok := v.([]interface{}) + if !ok { + return fmt.Errorf("decoding x5chain: got %T, want []interface", v) + } + + if err := o.extractX5Chain(arr); err != nil { + return err + } + } + + return nil +} + +func (o *SignedCorim) extractX5Chain(arr []interface{}) error { + var buf bytes.Buffer + + for i, elem := range arr { + cert, ok := elem.([]byte) + if !ok { + return fmt.Errorf("accessing x5chain[%d]: got %T, want []byte", i, elem) + } + + switch i { + case 0: + if err := o.AddSigningCert(cert); err != nil { + return fmt.Errorf("decoding x5chain[0]: %w", err) + } + default: + buf.Write(cert) + } + + } + + if buf.Len() > 0 { + if err := o.AddIntermediateCerts(buf.Bytes()); err != nil { + return fmt.Errorf("decoding x5chain: %w", err) + } + } + return nil } @@ -141,7 +181,7 @@ func (o *SignedCorim) AddSigningCert(der []byte) error { return fmt.Errorf("invalid signing certificate: %w", err) } - o.signingCert = cert + o.SigningCert = cert return nil } @@ -162,7 +202,7 @@ func (o *SignedCorim) AddIntermediateCerts(der []byte) error { return errors.New("no certificates found in intermediate cert data") } - o.intermediateCerts = certs + o.IntermediateCerts = certs return nil } @@ -200,9 +240,9 @@ func (o *SignedCorim) Sign(signer cose.Signer) ([]byte, error) { o.message.Headers.Protected[cose.HeaderLabelContentType] = ContentType o.message.Headers.Protected[HeaderLabelCorimMeta] = metaCBOR - if o.signingCert != nil { - certChain := [][]byte{o.signingCert.Raw} - for _, cert := range o.intermediateCerts { + if o.SigningCert != nil { + certChain := [][]byte{o.SigningCert.Raw} + for _, cert := range o.IntermediateCerts { certChain = append(certChain, cert.Raw) } o.message.Headers.Protected[cose.HeaderLabelX5Chain] = certChain diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 19c737e5..df0d3f8a 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -17,6 +17,14 @@ import ( ) var ( + testEndEntityKey = []byte(`{ + "kty": "EC", + "crv": "P-256", + "x": "Cc8hy7bQIFZJBtS1pQW9E0LT56doX04VB_lWX3S0fJA", + "y": "ggEG3MHZL7QSJk4Mfepi-GcmUiaPKysielICBFBxBz0", + "d": "VpaDza6El3l6OZDFvkebEu94Tg1n1b8J7JjBiOIXGKY" + }`) + testES256Key = []byte(`{ "kty": "EC", "crv": "P-256", @@ -560,11 +568,11 @@ func TestSignedCorim_AddSigningCert(t *testing.T) { if tt.wantErr { assert.Error(t, err) - assert.Nil(t, s.signingCert) + assert.Nil(t, s.SigningCert) assert.EqualError(t, err, tt.errMsg) } else { assert.NoError(t, err) - assert.NotNil(t, s.signingCert) + assert.NotNil(t, s.SigningCert) } }) } @@ -619,17 +627,60 @@ func TestSignedCorim_AddIntermediateCerts(t *testing.T) { if tt.wantErr { assert.Error(t, err) - assert.Empty(t, s.intermediateCerts) + assert.Empty(t, s.IntermediateCerts) assert.EqualError(t, err, tt.errMsg) } else { assert.NoError(t, err) - assert.NotEmpty(t, s.intermediateCerts) + assert.NotEmpty(t, s.IntermediateCerts) if tt.certCount > 0 { - assert.Equal(t, tt.certCount, len(s.intermediateCerts), + assert.Equal(t, tt.certCount, len(s.IntermediateCerts), "Should have %d certificates", tt.certCount) } } }) } } + +func TestSignedCorim_SignVerify_with_x5chain_ok(t *testing.T) { + signer, err := NewSignerFromJWK(testEndEntityKey) + require.NoError(t, err) + + var signedCorimIn SignedCorim + + signedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) + signedCorimIn.Meta = *metaGood(t) + + endEntityCertPath := filepath.Join("..", "misc", "endEntity.der") + endEntityCert, err := os.ReadFile(endEntityCertPath) + require.NoError(t, err, "Failed to read EE certificate") + + err = signedCorimIn.AddSigningCert(endEntityCert) + require.NoError(t, err, "Failed to add EE certificate") + + certChain, err := concatFiles( + filepath.Join("..", "misc", "intermediateCA.der"), + filepath.Join("..", "misc", "rootCA.der")) + require.NoError(t, err, "Failed to read certificate chain") + + err = signedCorimIn.AddIntermediateCerts(certChain) + require.NoError(t, err, "Failed to add cert chain") + + cbor, err := signedCorimIn.Sign(signer) + assert.Nil(t, err) + + var signedCorimOut SignedCorim + + fmt.Printf("signed-corim: %x\n", cbor) + + err = signedCorimOut.FromCOSE(cbor) + assert.Nil(t, err) + + pk, err := NewPublicKeyFromJWK(testEndEntityKey) + require.NoError(t, err) + + err = signedCorimOut.Verify(pk) + assert.Nil(t, err) + + assert.Equal(t, signedCorimIn.SigningCert, signedCorimOut.SigningCert) +} From f3965c10f59a744542c602ff8bafcb1b9922256a Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Wed, 5 Mar 2025 17:02:56 +0100 Subject: [PATCH 082/110] refactor(corim): process headers logics Clean up header processing, in particular extract CoRIM meta handling into its method, plus additional reflow for consistency. Signed-off-by: Thomas Fossati --- corim/signedcorim.go | 46 +++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/corim/signedcorim.go b/corim/signedcorim.go index fa9f20ff..45219559 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -27,9 +27,9 @@ var ( type SignedCorim struct { UnsignedCorim UnsignedCorim Meta Meta - message *cose.Sign1Message SigningCert *x509.Certificate IntermediateCerts []*x509.Certificate + message *cose.Sign1Message } // NewSignedCorim instantiates an empty SignedCorim @@ -62,24 +62,41 @@ func (o *SignedCorim) processHdrs() error { return errors.New("missing mandatory protected header") } - v, ok := hdr.Protected[cose.HeaderLabelContentType] - if !ok { + if v, ok := hdr.Protected[cose.HeaderLabelContentType]; ok { + if v != ContentType { + return fmt.Errorf("expecting content type %q, got %q instead", ContentType, v) + } + } else { return errors.New("missing mandatory content type") } - if v != ContentType { - return fmt.Errorf("expecting content type %q, got %q instead", ContentType, v) - } - // TODO(tho) key id is apparently mandatory, which doesn't look right. // TODO(tho) Check with the CoRIM design team. // See https://github.com/veraison/corim/issues/14 - v, ok = hdr.Protected[HeaderLabelCorimMeta] - if !ok { + if v, ok := hdr.Protected[HeaderLabelCorimMeta]; ok { + if err := o.extractMeta(v); err != nil { + return err + } + } else { return errors.New("missing mandatory corim.meta") } + if v, ok := hdr.Protected[cose.HeaderLabelX5Chain]; ok { + arr, ok := v.([]interface{}) + if !ok { + return fmt.Errorf("decoding x5chain: got %T, want []interface{}", v) + } + + if err := o.extractX5Chain(arr); err != nil { + return err + } + } + + return nil +} + +func (o *SignedCorim) extractMeta(v interface{}) error { metaCBOR, ok := v.([]byte) if !ok { return fmt.Errorf("expecting CBOR-encoded CoRIM Meta, got %T instead", v) @@ -94,17 +111,6 @@ func (o *SignedCorim) processHdrs() error { o.Meta = meta - if v, ok := hdr.Protected[cose.HeaderLabelX5Chain]; ok { - arr, ok := v.([]interface{}) - if !ok { - return fmt.Errorf("decoding x5chain: got %T, want []interface", v) - } - - if err := o.extractX5Chain(arr); err != nil { - return err - } - } - return nil } From 7db43426c5b5a7bc976faa37661f8a8c34d768ce Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Wed, 5 Mar 2025 17:31:20 +0100 Subject: [PATCH 083/110] test(corim): add missing assertion in x5chain test Signed-off-by: Thomas Fossati --- corim/signedcorim_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index df0d3f8a..49d70c8b 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -683,4 +683,5 @@ func TestSignedCorim_SignVerify_with_x5chain_ok(t *testing.T) { assert.Nil(t, err) assert.Equal(t, signedCorimIn.SigningCert, signedCorimOut.SigningCert) + assert.Equal(t, signedCorimIn.IntermediateCerts, signedCorimOut.IntermediateCerts) } From bf462572064155b0846675a2107ee403b588d619 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 16:34:23 +0000 Subject: [PATCH 084/110] chore(deps): Bump golang.org/x/crypto from 0.12.0 to 0.31.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.12.0 to 0.31.0. - [Commits](https://github.com/golang/crypto/compare/v0.12.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1e3a1cfc..2c8dff96 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/lestrrat-go/option v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.31.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 991148ed..1fef75a7 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca/go.mod h1:d5jt76uM github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 40f7023f108373f6fea678a07b729e01518735ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 17:12:38 +0000 Subject: [PATCH 085/110] chore(deps): Bump github.com/lestrrat-go/jwx/v2 from 2.0.8 to 2.0.21 Bumps [github.com/lestrrat-go/jwx/v2](https://github.com/lestrrat-go/jwx) from 2.0.8 to 2.0.21. - [Release notes](https://github.com/lestrrat-go/jwx/releases) - [Changelog](https://github.com/lestrrat-go/jwx/blob/v2.0.21/Changes) - [Commits](https://github.com/lestrrat-go/jwx/compare/v2.0.8...v2.0.21) --- updated-dependencies: - dependency-name: github.com/lestrrat-go/jwx/v2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 16 +++++++++------- go.sum | 45 ++++++++++++++++++--------------------------- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 2c8dff96..5b2bf26d 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.22 require ( github.com/fxamacker/cbor/v2 v2.5.0 github.com/google/uuid v1.3.0 - github.com/lestrrat-go/jwx/v2 v2.0.8 + github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/spf13/cast v1.4.1 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.9.0 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff github.com/veraison/go-cose v1.2.1 github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca @@ -15,17 +15,19 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/goccy/go-json v0.9.11 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/kr/pretty v0.2.0 // indirect - github.com/lestrrat-go/blackmagic v1.0.1 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc v1.0.4 // indirect + github.com/lestrrat-go/httprc v1.0.5 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/option v1.0.0 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sys v0.28.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1fef75a7..2aaa8ae4 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,14 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= @@ -17,32 +16,30 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= -github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= -github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= +github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.0.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9HR/Q= -github.com/lestrrat-go/jwx/v2 v2.0.8/go.mod h1:zLxnyv9rTlEvOUHbc48FAfIL8iYu2hHvIRaTFGc8mT0= -github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/jwx/v2 v2.0.21 h1:jAPKupy4uHgrHFEdjVjNkUgoBKtVDgrQPB/h55FHrR0= +github.com/lestrrat-go/jwx/v2 v2.0.21/go.mod h1:09mLW8zto6bWL9GbwnqAli+ArLf+5M33QLQPDggkUWM= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff h1:r6I2eJL/z8dp5flsQIKHMeDjyV6UO8If3MaVBLvTjF4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= github.com/veraison/go-cose v1.2.1 h1:Gj4x20D0YP79J2+cK3anjGEMwIkg2xX+TKVVGUXwNAc= @@ -51,16 +48,10 @@ github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca h1:osmCKwWO/xM68Kz github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca/go.mod h1:d5jt76uMNbTfQ+f2qU4Lt8RvWOTsv6PFgstIM1QdMH0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From ca220cc2895f70839310c91d6d453e0737866592 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Wed, 5 Mar 2025 18:19:49 +0100 Subject: [PATCH 086/110] =?UTF-8?q?chore(build):=20use=20the=20version=20s?= =?UTF-8?q?yntax=20=E2=80=981.N.P=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5b2bf26d..cce1748c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/veraison/corim -go 1.22 +go 1.22.0 require ( github.com/fxamacker/cbor/v2 v2.5.0 From 328f8d79877f44e1b3dba5238c6ac44b33197e8e Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Thu, 6 Mar 2025 16:52:25 +0100 Subject: [PATCH 087/110] doc(corim): update ref to upstream key-id issue Signed-off-by: Thomas Fossati --- corim/signedcorim.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 45219559..e605603c 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -72,7 +72,7 @@ func (o *SignedCorim) processHdrs() error { // TODO(tho) key id is apparently mandatory, which doesn't look right. // TODO(tho) Check with the CoRIM design team. - // See https://github.com/veraison/corim/issues/14 + // See https://github.com/ietf-rats-wg/draft-ietf-rats-corim/issues/363 if v, ok := hdr.Protected[HeaderLabelCorimMeta]; ok { if err := o.extractMeta(v); err != nil { From 0bbdd6c785266983a029b64e186626f8c464d57d Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Thu, 6 Mar 2025 17:40:14 +0100 Subject: [PATCH 088/110] fix(corim): handle silly RFC9360 microoptimisation x5chain is of type COSE_X509, which is defined as: COSE_X509 = bstr / [ 2*certs: bstr ] If there is only one element, it should be encoded as a simple bstr instead of being represented as an array. Signed-off-by: Thomas Fossati --- corim/signedcorim.go | 63 +++++++++++++++++++++++---------------- corim/signedcorim_test.go | 36 ++++++++++++++++++++++ 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/corim/signedcorim.go b/corim/signedcorim.go index e605603c..65d2577a 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -82,13 +82,9 @@ func (o *SignedCorim) processHdrs() error { return errors.New("missing mandatory corim.meta") } + // Process optional x5chain if v, ok := hdr.Protected[cose.HeaderLabelX5Chain]; ok { - arr, ok := v.([]interface{}) - if !ok { - return fmt.Errorf("decoding x5chain: got %T, want []interface{}", v) - } - - if err := o.extractX5Chain(arr); err != nil { + if err := o.extractX5Chain(v); err != nil { return err } } @@ -114,30 +110,38 @@ func (o *SignedCorim) extractMeta(v interface{}) error { return nil } -func (o *SignedCorim) extractX5Chain(arr []interface{}) error { +func (o *SignedCorim) extractX5Chain(x5chain interface{}) error { var buf bytes.Buffer - for i, elem := range arr { - cert, ok := elem.([]byte) - if !ok { - return fmt.Errorf("accessing x5chain[%d]: got %T, want []byte", i, elem) - } + switch t := x5chain.(type) { + case []interface{}: + for i, elem := range t { + cert, ok := elem.([]byte) + if !ok { + return fmt.Errorf("accessing x5chain[%d]: got %T, want []byte", i, elem) + } - switch i { - case 0: - if err := o.AddSigningCert(cert); err != nil { - return fmt.Errorf("decoding x5chain[0]: %w", err) + switch i { + case 0: + if err := o.AddSigningCert(cert); err != nil { + return fmt.Errorf("decoding x5chain: %w", err) + } + default: + buf.Write(cert) } - default: - buf.Write(cert) } - } - - if buf.Len() > 0 { - if err := o.AddIntermediateCerts(buf.Bytes()); err != nil { + if buf.Len() > 0 { + if err := o.AddIntermediateCerts(buf.Bytes()); err != nil { + return fmt.Errorf("decoding x5chain: %w", err) + } + } + case []byte: + if err := o.AddSigningCert(t); err != nil { return fmt.Errorf("decoding x5chain: %w", err) } + default: + return fmt.Errorf("decoding x5chain: got %T, want []interface{} or []byte", t) } return nil @@ -247,11 +251,18 @@ func (o *SignedCorim) Sign(signer cose.Signer) ([]byte, error) { o.message.Headers.Protected[HeaderLabelCorimMeta] = metaCBOR if o.SigningCert != nil { - certChain := [][]byte{o.SigningCert.Raw} - for _, cert := range o.IntermediateCerts { - certChain = append(certChain, cert.Raw) + // COSE_X509 = bstr / [ 2*certs: bstr ] + // + // handle alt (1): bstr + if len(o.IntermediateCerts) == 0 { + o.message.Headers.Protected[cose.HeaderLabelX5Chain] = o.SigningCert.Raw + } else { // handle alt (2): [ 2*certs: bstr ] + certChain := [][]byte{o.SigningCert.Raw} + for _, cert := range o.IntermediateCerts { + certChain = append(certChain, cert.Raw) + } + o.message.Headers.Protected[cose.HeaderLabelX5Chain] = certChain } - o.message.Headers.Protected[cose.HeaderLabelX5Chain] = certChain } err = o.message.Sign(rand.Reader, NoExternalData, signer) diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 49d70c8b..1016d76f 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -685,3 +685,39 @@ func TestSignedCorim_SignVerify_with_x5chain_ok(t *testing.T) { assert.Equal(t, signedCorimIn.SigningCert, signedCorimOut.SigningCert) assert.Equal(t, signedCorimIn.IntermediateCerts, signedCorimOut.IntermediateCerts) } + +func TestSignedCorim_SignVerify_with_single_cert_x5chain_ok(t *testing.T) { + signer, err := NewSignerFromJWK(testEndEntityKey) + require.NoError(t, err) + + var signedCorimIn SignedCorim + + signedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) + signedCorimIn.Meta = *metaGood(t) + + endEntityCertPath := filepath.Join("..", "misc", "endEntity.der") + endEntityCert, err := os.ReadFile(endEntityCertPath) + require.NoError(t, err, "Failed to read EE certificate") + + err = signedCorimIn.AddSigningCert(endEntityCert) + require.NoError(t, err, "Failed to add EE certificate") + + cbor, err := signedCorimIn.Sign(signer) + assert.Nil(t, err) + + var signedCorimOut SignedCorim + + fmt.Printf("signed-corim: %x\n", cbor) + + err = signedCorimOut.FromCOSE(cbor) + assert.Nil(t, err) + + pk, err := NewPublicKeyFromJWK(testEndEntityKey) + require.NoError(t, err) + + err = signedCorimOut.Verify(pk) + assert.Nil(t, err) + + assert.Equal(t, signedCorimIn.SigningCert, signedCorimOut.SigningCert) + assert.Equal(t, signedCorimIn.IntermediateCerts, signedCorimOut.IntermediateCerts) +} From c37ac860ac4db0d8e7e283b69974d23d06a15e08 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Tue, 18 Mar 2025 12:23:08 +0100 Subject: [PATCH 089/110] fix(corim): missing check when adding x5chain If intermediate certificates are provided, the EE certificate must also be provided. Signed-off-by: Thomas Fossati --- corim/signedcorim.go | 2 ++ corim/signedcorim_test.go | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 65d2577a..3675bdf6 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -263,6 +263,8 @@ func (o *SignedCorim) Sign(signer cose.Signer) ([]byte, error) { } o.message.Headers.Protected[cose.HeaderLabelX5Chain] = certChain } + } else if o.IntermediateCerts != nil { + return nil, errors.New("intermediate certificates supplied but no signing certificate") } err = o.message.Sign(rand.Reader, NoExternalData, signer) diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 1016d76f..eee2268a 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -721,3 +721,24 @@ func TestSignedCorim_SignVerify_with_single_cert_x5chain_ok(t *testing.T) { assert.Equal(t, signedCorimIn.SigningCert, signedCorimOut.SigningCert) assert.Equal(t, signedCorimIn.IntermediateCerts, signedCorimOut.IntermediateCerts) } + +func TestSignedCorim_Sign_with_x5chain_fail_missing_ee_cert(t *testing.T) { + signer, err := NewSignerFromJWK(testEndEntityKey) + require.NoError(t, err) + + var signedCorimIn SignedCorim + + signedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) + signedCorimIn.Meta = *metaGood(t) + + certChain, err := concatFiles( + filepath.Join("..", "misc", "intermediateCA.der"), + filepath.Join("..", "misc", "rootCA.der")) + require.NoError(t, err, "Failed to read certificate chain") + + err = signedCorimIn.AddIntermediateCerts(certChain) + require.NoError(t, err, "Failed to add cert chain") + + _, err = signedCorimIn.Sign(signer) + assert.EqualError(t, err, "intermediate certificates supplied but no signing certificate") +} From 4d7a61289633934302b5109a97b85fce77b25262 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Wed, 26 Feb 2025 10:40:04 +0000 Subject: [PATCH 090/110] feat: Introduce Conditional Endorsement Series Triple Fixes #169 Signed-off-by: Yogesh Deshpande --- comid/comid.go | 23 +- comid/cond_endorse_series_triple.go | 160 ++++++ comid/cond_endorse_series_triple_test.go | 47 ++ comid/example_test.go | 543 ++++++++++++------ comid/extensions.go | 20 +- comid/tdx-profile/example_pce_refval_test.go | 2 +- comid/tdx-profile/example_qe_refval_test.go | 2 +- comid/tdx-profile/example_seam_refval_test.go | 6 +- .../testcases/comid-cond-endorse-series.cbor | Bin 0 -> 335 bytes comid/testcases/regen-from-src.sh | 0 .../src/comid-cond-endorse-series.diag | 119 ++++ comid/triples.go | 73 ++- comid/triples_test.go | 16 +- corim/example_profile_test.go | 2 +- corim/unsignedcorim_test.go | 6 +- extensions/collection.go | 2 +- 16 files changed, 801 insertions(+), 220 deletions(-) create mode 100644 comid/cond_endorse_series_triple.go create mode 100644 comid/cond_endorse_series_triple_test.go create mode 100644 comid/testcases/comid-cond-endorse-series.cbor mode change 100644 => 100755 comid/testcases/regen-from-src.sh create mode 100644 comid/testcases/src/comid-cond-endorse-series.diag diff --git a/comid/comid.go b/comid/comid.go index 51ca1fe9..34fade36 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -184,7 +184,7 @@ func (o *Comid) AddLinkedTag(tagID interface{}, rel Rel) *Comid { // AddReferenceValue adds the supplied reference value to the // reference-triples list of the target Comid. -func (o *Comid) AddReferenceValue(val ValueTriple) *Comid { +func (o *Comid) AddReferenceValue(val *ValueTriple) *Comid { if o != nil { if o.Triples.ReferenceValues == nil { o.Triples.ReferenceValues = NewValueTriples() @@ -199,7 +199,7 @@ func (o *Comid) AddReferenceValue(val ValueTriple) *Comid { // AddEndorsedValue adds the supplied endorsed value to the // endorsed-triples list of the target Comid. -func (o *Comid) AddEndorsedValue(val ValueTriple) *Comid { +func (o *Comid) AddEndorsedValue(val *ValueTriple) *Comid { if o != nil { if o.Triples.EndorsedValues == nil { o.Triples.EndorsedValues = NewValueTriples() @@ -214,7 +214,7 @@ func (o *Comid) AddEndorsedValue(val ValueTriple) *Comid { // AddAttestVerifKey adds the supplied verification key to the // attest-key-triples list of the target Comid. -func (o *Comid) AddAttestVerifKey(val KeyTriple) *Comid { +func (o *Comid) AddAttestVerifKey(val *KeyTriple) *Comid { if o != nil { if o.Triples.AttestVerifKeys == nil { o.Triples.AttestVerifKeys = NewKeyTriples() @@ -229,7 +229,7 @@ func (o *Comid) AddAttestVerifKey(val KeyTriple) *Comid { // AddDevIdentityKey adds the supplied identity key to the // identity-triples list of the target Comid. -func (o *Comid) AddDevIdentityKey(val KeyTriple) *Comid { +func (o *Comid) AddDevIdentityKey(val *KeyTriple) *Comid { if o != nil { if o.Triples.DevIdentityKeys == nil { o.Triples.DevIdentityKeys = NewKeyTriples() @@ -242,6 +242,21 @@ func (o *Comid) AddDevIdentityKey(val KeyTriple) *Comid { return o } +// AddCondEndorseSeries adds the supplied conditional series triple to the +// conditional series triple list of the target Comid. +func (o *Comid) AddCondEndorseSeries(val *CondEndorseSeriesTriple) *Comid { + if o != nil { + if o.Triples.CondEndorseSeries == nil { + o.Triples.CondEndorseSeries = NewCondEndorseSeriesTriples() + } + + if o.Triples.AddCondEndorseSeries(val) == nil { + return nil + } + } + return o +} + // nolint:gocritic func (o Comid) Valid() error { if err := o.TagIdentity.Valid(); err != nil { diff --git a/comid/cond_endorse_series_triple.go b/comid/cond_endorse_series_triple.go new file mode 100644 index 00000000..d54d768b --- /dev/null +++ b/comid/cond_endorse_series_triple.go @@ -0,0 +1,160 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "fmt" + + "github.com/veraison/corim/extensions" +) + +// A Stateful Environment is an Environment in a known reference state +type StatefulEnv = ValueTriple + +// A Conditional Series Record, has a series of conditions identified by +// the selection which are matched with the Attester Actual State(from Evidence) +// First successful match terminates matching and corresponding addition are added +// as Endorsements +type CondEndorseSeriesRecord struct { + Selection Measurements `json:"selection"` + Addition Measurements `json:"addition"` +} + +func (o CondEndorseSeriesRecord) Valid() error { + if err := o.Selection.Valid(); err != nil { + return fmt.Errorf("conditional series record selection validation failed: %w", err) + } + + if err := o.Addition.Valid(); err != nil { + return fmt.Errorf("conditional series record addition validation failed: %w", err) + } + return nil +} + +// nolint:gocritic +func (o CondEndorseSeriesRecord) GetExtensions() extensions.IMapValue { + // Extensions are always the same for Selection and Addition + return o.Selection.GetExtensions() +} + +func (o *CondEndorseSeriesRecord) RegisterExtensions(exts extensions.Map) error { + if err := o.Selection.RegisterExtensions(exts); err != nil { + return fmt.Errorf("selection: %w", err) + } + if err := o.Addition.RegisterExtensions(exts); err != nil { + return fmt.Errorf("addition: %w", err) + } + + return nil +} + +type CondEndorseSeriesRecords extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord] + +func NewCondEndorseSeriesRecords() *CondEndorseSeriesRecords { + return (*CondEndorseSeriesRecords)(extensions.NewCollection[CondEndorseSeriesRecord]()) +} + +func (o *CondEndorseSeriesRecords) IsEmpty() bool { + return (*extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).IsEmpty() +} + +func (o *CondEndorseSeriesRecords) Add(val *CondEndorseSeriesRecord) *CondEndorseSeriesRecords { + ret := (*extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).Add(val) + return (*CondEndorseSeriesRecords)(ret) +} + +func (o *CondEndorseSeriesRecords) GetExtensions() extensions.IMapValue { + return (*extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).GetExtensions() +} + +func (o *CondEndorseSeriesRecords) RegisterExtensions(exts extensions.Map) error { + return (*extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).RegisterExtensions(exts) +} + +func (o *CondEndorseSeriesRecords) Valid() error { + return (*extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).Valid() +} + +// The Conditional Endorsement Series Triple is used to assert endorsed values based +// on an initial condition match (specified in condition:) followed by a series +// condition match (specified in selection: inside conditional-series-record). +type CondEndorseSeriesTriple struct { + _ struct{} `cbor:",toarray"` + Condition StatefulEnv `json:"statefulenv"` + Series CondEndorseSeriesRecords `json:"series"` +} + +// RegisterExtensions accepts MVal and MFlag Extension points, that will be registered with +// all Measurements contained within CondEndorseSeriesTriple structure +func (o *CondEndorseSeriesTriple) RegisterExtensions(exts extensions.Map) error { + if err := o.Condition.RegisterExtensions(exts); err != nil { + return fmt.Errorf("condition: %w", err) + } + if err := o.Series.RegisterExtensions(exts); err != nil { + return fmt.Errorf("selection: %w", err) + } + + return nil +} + +// nolint:gocritic +func (o CondEndorseSeriesTriple) Valid() error { + if err := o.Condition.Valid(); err != nil { + return fmt.Errorf("stateful environment validation failed: %w", err) + } + if err := o.Series.Valid(); err != nil { + return fmt.Errorf("conditional series validation failed: %w", err) + } + + return nil +} + +func (o *CondEndorseSeriesTriple) GetExtensions() extensions.IMapValue { + return o.Series.GetExtensions() +} + +// CondEndorseSeriesTriples is a container for CondEndorseSeriesTriple instances and their extensions. +// It is a thin wrapper around extensions.Collection. +type CondEndorseSeriesTriples extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple] + +func NewCondEndorseSeriesTriples() *CondEndorseSeriesTriples { + return (*CondEndorseSeriesTriples)(extensions.NewCollection[CondEndorseSeriesTriple]()) +} + +func (o *CondEndorseSeriesTriples) GetExtensions() extensions.IMapValue { + return (*extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).GetExtensions() +} + +func (o *CondEndorseSeriesTriples) RegisterExtensions(exts extensions.Map) error { + return (*extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).RegisterExtensions(exts) +} + +func (o CondEndorseSeriesTriples) Valid() error { + return (extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).Valid() +} + +func (o *CondEndorseSeriesTriples) IsEmpty() bool { + return (*extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).IsEmpty() +} + +func (o *CondEndorseSeriesTriples) Add(val *CondEndorseSeriesTriple) *CondEndorseSeriesTriples { + ret := (*extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).Add(val) + return (*CondEndorseSeriesTriples)(ret) +} + +func (o CondEndorseSeriesTriples) MarshalCBOR() ([]byte, error) { + return (extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).MarshalCBOR() +} + +func (o *CondEndorseSeriesTriples) UnmarshalCBOR(data []byte) error { + return (*extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).UnmarshalCBOR(data) +} + +func (o CondEndorseSeriesTriples) MarshalJSON() ([]byte, error) { + return (extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).MarshalJSON() +} + +func (o *CondEndorseSeriesTriples) UnmarshalJSON(data []byte) error { + return (*extensions.Collection[CondEndorseSeriesTriple, *CondEndorseSeriesTriple])(o).UnmarshalJSON(data) +} diff --git a/comid/cond_endorse_series_triple_test.go b/comid/cond_endorse_series_triple_test.go new file mode 100644 index 00000000..557cf837 --- /dev/null +++ b/comid/cond_endorse_series_triple_test.go @@ -0,0 +1,47 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/extensions" +) + +func Test_CondEndorseSeries_NewCondEndorseSeriesTriples_OK(t *testing.T) { + c := NewCondEndorseSeriesTriples() + require.NotNil(t, c) +} + +func Test_CondEndorseSeries_Valid_NOK(t *testing.T) { + c := NewCondEndorseSeriesTriples() + expectedErr := "error at index 0: stateful environment validation failed: environment validation failed: environment must not be empty" + series := &CondEndorseSeriesTriple{} + c.Add(series) + err := c.Valid() + assert.EqualError(t, err, expectedErr) +} + +type testExtensions struct { + TestSVN uint `cbor:"-72,keyasint,omitempty" json:"testsvn,omitempty"` +} + +func Test_CondEndorseSeries_RegisterExtensions(t *testing.T) { + extMap := extensions.NewMap(). + Add(ExtMval, &testExtensions{}) + series := &CondEndorseSeriesTriple{} + err := series.RegisterExtensions(extMap) + require.NoError(t, err) +} + +func Test_CondEndorseSeries_RegisterExtensions_NOK(t *testing.T) { + expectedErr := `condition: unexpected extension point: "ReferenceValue"` + extMap := extensions.NewMap(). + Add(ExtReferenceValue, &testExtensions{}) + series := &CondEndorseSeriesTriple{} + err := series.RegisterExtensions(extMap) + assert.EqualError(t, err, expectedErr) +} diff --git a/comid/example_test.go b/comid/example_test.go index 3012f08f..30e050eb 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -21,7 +21,7 @@ func Example_encode() { AddLinkedTag("my-ns:acme-roadrunner-base", RelSupplements). AddLinkedTag("my-ns:acme-roadrunner-old", RelReplaces). AddReferenceValue( - ValueTriple{ + &ValueTriple{ Environment: Environment{ Class: NewClassOID(TestOID). SetVendor("ACME Ltd."). @@ -49,7 +49,7 @@ func Example_encode() { }, ). AddEndorsedValue( - ValueTriple{ + &ValueTriple{ Environment: Environment{ Class: NewClassUUID(TestUUID). SetVendor("ACME Ltd."). @@ -77,7 +77,7 @@ func Example_encode() { }, ). AddAttestVerifKey( - KeyTriple{ + &KeyTriple{ Environment: Environment{ Instance: MustNewUUIDInstance(uuid.UUID(TestUUID)), }, @@ -87,7 +87,7 @@ func Example_encode() { ), }, ).AddDevIdentityKey( - KeyTriple{ + &KeyTriple{ Environment: Environment{ Instance: MustNewUEIDInstance(TestUEID), }, @@ -96,7 +96,59 @@ func Example_encode() { MustNewPKIXBase64Key(TestECPubKey), ), }, - ) + ). + AddCondEndorseSeries( + &CondEndorseSeriesTriple{ + Condition: ValueTriple{ + Environment: Environment{ + Class: NewClassOID(TestOID). + SetVendor("ACME Ltd."). + SetModel("RoadRunner"). + SetLayer(0). + SetIndex(1), + Instance: MustNewUEIDInstance(TestUEID), + Group: MustNewUUIDGroup(TestUUID), + }, + Measurements: *NewMeasurements(). + Add( + MustNewUUIDMeasurement(TestUUID). + SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). + SetSVN(2). + AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + SetFlagsTrue(FlagIsDebug). + SetFlagsFalse(FlagIsSecure). + SetSerialNumber("C02X70VHJHD5"). + SetUEID(TestUEID). + SetUUID(TestUUID). + SetMACaddr(MACaddr(TestMACaddr)). + SetIPaddr(TestIPaddr), + ), + }, + Series: *NewCondEndorseSeriesRecords(). + Add( + &CondEndorseSeriesRecord{ + Selection: *NewMeasurements(). + Add( + MustNewUUIDMeasurement(TestUUID). + SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). + SetSVN(2). + AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + SetFlagsTrue(FlagIsDebug). + SetFlagsFalse(FlagIsSecure), + ), + Addition: *NewMeasurements(). + Add( + MustNewUUIDMeasurement(TestUUID). + SetUEID(TestUEID). + SetMACaddr(MACaddr(TestMACaddr)). + SetIPaddr(TestIPaddr), + ), + }, + ), + }, + ) cbor, err := comid.ToCBOR() if err == nil { @@ -109,8 +161,8 @@ func Example_encode() { } // Output: - // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d - // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a5008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d08818282a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfaa16656616c75657381a2686164646974696f6e81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a3064802005e1000000001075020010db8000000000000000000000068094702deadbeefdead6973656c656374696f6e81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a501d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"conditional-endorsement-series":[{"statefulenv":{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]},"series":{"Values":[{"selection":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w=="}}],"addition":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","ueid":"At6tvu/erQ=="}}]}]}}]}} } func Example_encode_PSA() { @@ -118,7 +170,7 @@ func Example_encode_PSA() { SetTagIdentity("my-ns:acme-roadrunner-supplement", 0). AddEntity("ACME Ltd.", &TestRegID, RoleCreator, RoleTagCreator, RoleMaintainer). AddReferenceValue( - ValueTriple{ + &ValueTriple{ Environment: Environment{ Class: NewClassImplID(TestImplID). SetVendor("ACME Ltd."). @@ -140,7 +192,7 @@ func Example_encode_PSA() { }, ). AddAttestVerifKey( - KeyTriple{ + &KeyTriple{ Environment: Environment{ Instance: MustNewUEIDInstance(TestUEID), }, @@ -171,7 +223,7 @@ func Example_encode_PSA_attestation_verification() { SetTagIdentity("my-ns:acme-roadrunner-supplement", 0). AddEntity("ACME Ltd.", &TestRegID, RoleCreator, RoleTagCreator, RoleMaintainer). AddAttestVerifKey( - KeyTriple{ + &KeyTriple{ Environment: Environment{ Instance: MustNewUEIDInstance(TestUEID), }, @@ -200,175 +252,301 @@ func Example_encode_PSA_attestation_verification() { func Example_decode_JSON() { j := ` { - "lang": "en-GB", - "tag-identity": { - "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", - "version": 1 - }, - "entities": [ - { - "name": "ACME Ltd.", - "regid": "https://acme.example", - "roles": [ "tagCreator" ] - }, - { - "name": "EMCA Ltd.", - "regid": "https://emca.example", - "roles": [ "maintainer", "creator" ] - } - ], - "linked-tags": [ - { - "target": "6F7D8D2F-EAEC-4A15-BB46-1E4DCB85DDFF", - "rel": "replaces" - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "uuid", - "value": "83294297-97EB-42EF-8A72-AE9FEA002750" - }, - "vendor": "ACME", - "model": "RoadRunner Boot ROM", - "layer": 0, - "index": 0 - }, - "instance": { - "type": "ueid", - "value": "Ad6tvu/erb7v3q2+796tvu8=" - } - }, - "measurements": [ - { - "value": { - "digests": [ - "sha-256:3q2+7w==" - ] - } - } - ] - }, - { - "environment": { - "class": { - "id": { - "type": "psa.impl-id", - "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" - }, - "vendor": "PSA-X", - "model": "Turbo PRoT" - } - }, - "measurements": [ - { - "key": { - "type": "psa.refval-id", - "value": { - "label": "PRoT", - "version": "1.3.5", - "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" - } - }, - "value": { - "digests": [ - "sha-256:3q2+7w==" - ], - "svn": { - "type": "exact-value", - "value": 1 - }, - "mac-addr": "00:00:5e:00:53:01" - } - } - ] - } - ], - "endorsed-values": [ - { - "environment": { - "class": { - "id": { - "type": "oid", - "value": "2.16.840.1.101.3.4.2.1" - } - }, - "instance": { - "type": "uuid", - "value": "9090B8D3-3B17-474C-A0B9-6F54731CAB72" - } - }, - "measurements": [ - { - "value": { - "mac-addr": "00:00:5e:00:53:01", - "ip-addr": "2001:4860:0:2001::68", - "serial-number": "C02X70VHJHD5", - "ueid": "Ad6tvu/erb7v3q2+796tvu8=", - "uuid": "9090B8D3-3B17-474C-A0B9-6F54731CAB72", - "raw-value": { - "type": "bytes", - "value": "cmF3dmFsdWUKcmF3dmFsdWUK" - }, - "raw-value-mask": "qg==", - "op-flags": [ "notSecure" ], - "digests": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=", - "sha-384;S1bPoH+usqtX3pIeSpfWVRRLVGRw66qrb3HA21GN31tKX7KPsq0bSTQmRCTrHlqG" - ], - "version": { - "scheme": "semaver", - "value": "1.2.3beta4" - }, - "svn": { - "type": "min-value", - "value": 10 - } - } - } - ] - } - ], - "attester-verification-keys": [ - { - "environment": { - "group": { - "type": "uuid", - "value": "83294297-97EB-42EF-8A72-AE9FEA002750" - } - }, - "verification-keys": [ - { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" - } - ] - } - ], - "dev-identity-keys": [ - { - "environment": { - "instance": { - "type": "uuid", - "value": "4ECCE47C-85F2-4FD9-9EC6-00DEB72DA707" - } - }, - "verification-keys": [ - { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" - }, - { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" - } - ] - } - ] - } + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 1 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator" + ] + }, + { + "name": "EMCA Ltd.", + "regid": "https://emca.example", + "roles": [ + "maintainer", + "creator" + ] + } + ], + "linked-tags": [ + { + "target": "6F7D8D2F-EAEC-4A15-BB46-1E4DCB85DDFF", + "rel": "replaces" + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "uuid", + "value": "83294297-97EB-42EF-8A72-AE9FEA002750" + }, + "vendor": "ACME", + "model": "RoadRunner Boot ROM", + "layer": 0, + "index": 0 + }, + "instance": { + "type": "ueid", + "value": "Ad6tvu/erb7v3q2+796tvu8=" + } + }, + "measurements": [ + { + "value": { + "digests": [ + "sha-256:3q2+7w==" + ] + } + } + ] + }, + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + }, + "vendor": "PSA-X", + "model": "Turbo PRoT" + } + }, + "measurements": [ + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "PRoT", + "version": "1.3.5", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } + }, + "value": { + "digests": [ + "sha-256:3q2+7w==" + ], + "svn": { + "type": "exact-value", + "value": 1 + }, + "mac-addr": "00:00:5e:00:53:01" + } + } + ] + } + ], + "endorsed-values": [ + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.16.840.1.101.3.4.2.1" + } + }, + "instance": { + "type": "uuid", + "value": "9090B8D3-3B17-474C-A0B9-6F54731CAB72" + } + }, + "measurements": [ + { + "value": { + "mac-addr": "00:00:5e:00:53:01", + "ip-addr": "2001:4860:0:2001::68", + "serial-number": "C02X70VHJHD5", + "ueid": "Ad6tvu/erb7v3q2+796tvu8=", + "uuid": "9090B8D3-3B17-474C-A0B9-6F54731CAB72", + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + }, + "raw-value-mask": "qg==", + "op-flags": [ + "notSecure" + ], + "digests": [ + "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=", + "sha-384;S1bPoH+usqtX3pIeSpfWVRRLVGRw66qrb3HA21GN31tKX7KPsq0bSTQmRCTrHlqG" + ], + "version": { + "scheme": "semaver", + "value": "1.2.3beta4" + }, + "svn": { + "type": "min-value", + "value": 10 + } + } + } + ] + } + ], + "attester-verification-keys": [ + { + "environment": { + "group": { + "type": "uuid", + "value": "83294297-97EB-42EF-8A72-AE9FEA002750" + } + }, + "verification-keys": [ + { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" + } + ] + } + ], + "dev-identity-keys": [ + { + "environment": { + "instance": { + "type": "uuid", + "value": "4ECCE47C-85F2-4FD9-9EC6-00DEB72DA707" + } + }, + "verification-keys": [ + { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" + }, + { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" + } + ] + } + ], + "conditional-endorsement-series": [ + { + "statefulenv": { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.5.2.8192" + }, + "vendor": "ACME Ltd.", + "model": "RoadRunner", + "layer": 0, + "index": 1 + }, + "instance": { + "type": "ueid", + "value": "At6tvu/erQ==" + }, + "group": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + } + }, + "measurements": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "svn": { + "type": "exact-value", + "value": 1 + }, + "digests": [ + "sha-256-32;q83vAA==", + "sha-256-32;/////w==" + ], + "flags": { + "is-secure": false, + "is-debug": true + }, + "raw-value": { + "type": "bytes", + "value": "AQIDBA==" + }, + "raw-value-mask": "/////w==", + "serial-number": "C02X70VHJHD5", + "uuid": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + } + } + ] + }, + "series": { + "Values": [ + { + "selection": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "svn": { + "type": "exact-value", + "value": 2 + }, + "version": {"value": "2.0.0", "scheme": "semver" } + } + } + ], + "addition": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "mac-addr": "02:00:5e:10:00:00:00:01", + "ip-addr": "2001:db8::68", + "ueid": "At6tvu/erQ==" + } + } + ] + }, + { + "selection": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "svn": { + "type": "exact-value", + "value": 3 + }, + "version": {"value": "3.0.1", "scheme": "semver" } + } + } + ], + "addition": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "mac-addr": "02:00:5e:10:00:00:00:02", + "ip-addr": "2001:db8::69", + "ueid": "At6tvu/erQ==" + } + } + ] + } + ] + } + } + ] + } } ` comid := Comid{} @@ -407,6 +585,9 @@ var ( //go:embed testcases/comid-5.cbor testComid5 []byte + + //go:embed testcases/comid-cond-endorse-series.cbor + testComidCondEndorseSeries []byte ) func TestExample_decode_CBOR(_ *testing.T) { @@ -442,6 +623,10 @@ func TestExample_decode_CBOR(_ *testing.T) { descr: "Test with CoMID-5 Diag", inp: testComid5, }, + { + descr: "Test with CoMID-cond-endorse-series Diag", + inp: testComidCondEndorseSeries, + }, } for _, tv := range tvs { comid := Comid{} diff --git a/comid/extensions.go b/comid/extensions.go index 522ad6b3..584d3337 100644 --- a/comid/extensions.go +++ b/comid/extensions.go @@ -7,15 +7,17 @@ import ( ) const ( - ExtComid extensions.Point = "Comid" - ExtEntity extensions.Point = "ComidEntity" - ExtTriples extensions.Point = "Triples" - ExtReferenceValue extensions.Point = "ReferenceValue" - ExtReferenceValueFlags extensions.Point = "ReferenceValueFlags" - ExtEndorsedValue extensions.Point = "EndorsedValue" - ExtEndorsedValueFlags extensions.Point = "EndorsedValueFlags" - ExtMval extensions.Point = "Mval" - ExtFlags extensions.Point = "Flags" + ExtComid extensions.Point = "Comid" + ExtEntity extensions.Point = "ComidEntity" + ExtTriples extensions.Point = "Triples" + ExtReferenceValue extensions.Point = "ReferenceValue" + ExtReferenceValueFlags extensions.Point = "ReferenceValueFlags" + ExtEndorsedValue extensions.Point = "EndorsedValue" + ExtEndorsedValueFlags extensions.Point = "EndorsedValueFlags" + ExtCondEndorseSeriesValue extensions.Point = "CondEndorseSeriesValue" + ExtCondEndorseSeriesValueFlags extensions.Point = "CondEndorseSeriesValueFlags" + ExtMval extensions.Point = "Mval" + ExtFlags extensions.Point = "Flags" ) type IComidConstrainer interface { diff --git a/comid/tdx-profile/example_pce_refval_test.go b/comid/tdx-profile/example_pce_refval_test.go index 36568a38..d8dd1472 100644 --- a/comid/tdx-profile/example_pce_refval_test.go +++ b/comid/tdx-profile/example_pce_refval_test.go @@ -276,7 +276,7 @@ func Example_encode_tdx_pce_refval_with_profile() { } refVal.Measurements.Add(measurement) - m.Triples.AddReferenceValue(*refVal) + m.Triples.AddReferenceValue(refVal) err = setTDXPCEMvalExtension(&m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val) if err != nil { diff --git a/comid/tdx-profile/example_qe_refval_test.go b/comid/tdx-profile/example_qe_refval_test.go index 500ab937..f314fb11 100644 --- a/comid/tdx-profile/example_qe_refval_test.go +++ b/comid/tdx-profile/example_qe_refval_test.go @@ -187,7 +187,7 @@ func Example_encode_tdx_QE_refval_without_profile() { AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) refVal.Measurements.Add(measurement) - m.Triples.AddReferenceValue(*refVal) + m.Triples.AddReferenceValue(refVal) if err := m.RegisterExtensions(extMap); err != nil { panic(err) } diff --git a/comid/tdx-profile/example_seam_refval_test.go b/comid/tdx-profile/example_seam_refval_test.go index 9898b7a8..22388c64 100644 --- a/comid/tdx-profile/example_seam_refval_test.go +++ b/comid/tdx-profile/example_seam_refval_test.go @@ -78,7 +78,7 @@ func Example_encode_tdx_seam_refval_without_profile() { AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) refVal.Measurements.Add(measurement) - m.Triples.AddReferenceValue(*refVal) + m.Triples.AddReferenceValue(refVal) if err := m.RegisterExtensions(extMap); err != nil { panic(err) } @@ -136,7 +136,7 @@ func Example_encode_tdx_seam_refval_with_profile() { } refVal.Measurements.Add(measurement) - m.Triples.AddReferenceValue(*refVal) + m.Triples.AddReferenceValue(refVal) err = setTDXSeamMvalExtensions(&m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val) if err != nil { @@ -190,7 +190,7 @@ func Example_encode_tdx_seam_refval_direct() { } refVal.Measurements.Add(measurement) - m.Triples.AddReferenceValue(*refVal) + m.Triples.AddReferenceValue(refVal) err := m.Valid() if err != nil { diff --git a/comid/testcases/comid-cond-endorse-series.cbor b/comid/testcases/comid-cond-endorse-series.cbor new file mode 100644 index 0000000000000000000000000000000000000000..5d7019b60fc56b7df44ac20cce48dcf964cb6117 GIT binary patch literal 335 zcmZ3?xR9YjDmXE>ASYEJur$9UGcR4iH7_|Qu`E>~$kUgpaWO-RV{&dP;|+y^jFOUq zVk>=p5MM7jKbNVQk%5V25kq6sLWac*H}ZWF+B_KBe)uvnF|)8RW`L9_c;+QD6&M&A z8Jn1znOj&oIyt+zx-~8YTDy=T(TJn535#Avpk8NRR~&j5F)n0kY+{U1a9L%X_2sn4 zib`j_;Jc}X$A8T}vwnNLM^mJlZrH^xZ!&3BNF^l}r<$3>XQx)ir{x#v0zIXh wT$Gwvl3&!^1ab)DO(qRSu!|Nkq#EiO=ov6dIxxVcm|#*y7*fnIDPx!v0BN>sEdT%j literal 0 HcmV?d00001 diff --git a/comid/testcases/regen-from-src.sh b/comid/testcases/regen-from-src.sh old mode 100644 new mode 100755 diff --git a/comid/testcases/src/comid-cond-endorse-series.diag b/comid/testcases/src/comid-cond-endorse-series.diag new file mode 100644 index 00000000..352d9603 --- /dev/null +++ b/comid/testcases/src/comid-cond-endorse-series.diag @@ -0,0 +1,119 @@ +/ concise-mid-tag / { + / tag-identity / 1 : { + / tag-id / 0 : "Sample Quoting Enclave RIM" + }, + / entity / 2 : [ { + / entity-name / 0 : "Acme", + / reg-id / 1 : 32("https://Acme.com"), + / role / 2 : [ 1,0,2 ] / creator, tag-creator, maintainer / + } ], + / triples / 4 : { + / reference-triples / 0 : [ + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D0102030404'), / 2.16.840.1.113741.1.2.3.4.4 / + / vendor / 1 : "Acme Inc", + / model / 2 : "0123456789ABCDEF" + } + }, + [ + / measurement-map / { + / mval / 1 : { + / version-map / 0 : { + / version / 0 : "2" + } + } + } + ] + ] + ], + / conditional-endorsement-series-triples / 8 : [ + [ + / stateful-environment-record / [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D0102030401'), / 2.16.840.1.113741.1.2.3.4.1 / + / vendor / 1 : "ACME Inc", + / model / 2 : "0123456789ABCDEF" + } + }, + [ + / measurement-map / { + / mval / 1 : / measurement-values-map / { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + }, + / authorized-by / 2 : [ + / tagged-pkix-base64-key-type / 554("base64_key_for-RIM-creator") + ] + } + ] + ], + [ / *** series records *** / + [ / *** record 1 *** / + [ / selection / + / measurement-map / { + / measurement-values-map / 1 : { + / comid.svn / 1 : 552(1) + } + } + ], + [ / addition / + / measurement-map / { + / measurement-values-map / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + } + } + } + ] + ], + [ / *** record 2 *** / + [ / selection / + / measurement-map / { + / measurement-values-map / 1 : { + / comid.svn / 1 : 552(2) + } + } + ], + [ / addition / + / measurement-map / { + / measurement-values-map / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "2.0.0", + / comid.version-scheme / 1 : 16384 / semver / + } + } + } + ] + ], + [ / *** record 3 *** / + [ / selection / + / measurement-map / { + / measurement-values-map / 1 : { + / comid.svn / 1 : 552(3) + } + } + ], + [ / addition / + / measurement-map / { + / measurement-values-map / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "3.0.0", + / comid.version-scheme / 1 : 16384 / semver / + } + } + } + ] + ] + ] + ] + ] + } +} \ No newline at end of file diff --git a/comid/triples.go b/comid/triples.go index a937b7ef..9149dc66 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -11,11 +11,11 @@ import ( ) type Triples struct { - ReferenceValues *ValueTriples `cbor:"0,keyasint,omitempty" json:"reference-values,omitempty"` - EndorsedValues *ValueTriples `cbor:"1,keyasint,omitempty" json:"endorsed-values,omitempty"` - DevIdentityKeys *KeyTriples `cbor:"2,keyasint,omitempty" json:"dev-identity-keys,omitempty"` - AttestVerifKeys *KeyTriples `cbor:"3,keyasint,omitempty" json:"attester-verification-keys,omitempty"` - + ReferenceValues *ValueTriples `cbor:"0,keyasint,omitempty" json:"reference-values,omitempty"` + EndorsedValues *ValueTriples `cbor:"1,keyasint,omitempty" json:"endorsed-values,omitempty"` + DevIdentityKeys *KeyTriples `cbor:"2,keyasint,omitempty" json:"dev-identity-keys,omitempty"` + AttestVerifKeys *KeyTriples `cbor:"3,keyasint,omitempty" json:"attester-verification-keys,omitempty"` + CondEndorseSeries *CondEndorseSeriesTriples `cbor:"8,keyasint,omitempty" json:"conditional-endorsement-series,omitempty"` Extensions } @@ -23,6 +23,7 @@ type Triples struct { func (o *Triples) RegisterExtensions(exts extensions.Map) error { refValExts := extensions.NewMap() endValExts := extensions.NewMap() + conSeriesExts := extensions.NewMap() for p, v := range exts { switch p { @@ -36,6 +37,10 @@ func (o *Triples) RegisterExtensions(exts extensions.Map) error { endValExts[ExtMval] = v case ExtEndorsedValueFlags: endValExts[ExtFlags] = v + case ExtCondEndorseSeriesValue: + conSeriesExts[ExtMval] = v + case ExtCondEndorseSeriesValueFlags: + conSeriesExts[ExtFlags] = v default: return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) } @@ -56,7 +61,17 @@ func (o *Triples) RegisterExtensions(exts extensions.Map) error { o.EndorsedValues = NewValueTriples() } - if err := o.EndorsedValues.RegisterExtensions(refValExts); err != nil { + if err := o.EndorsedValues.RegisterExtensions(endValExts); err != nil { + return err + } + } + + if len(conSeriesExts) != 0 { + if o.CondEndorseSeries == nil { + o.CondEndorseSeries = NewCondEndorseSeriesTriples() + } + + if err := o.CondEndorseSeries.RegisterExtensions(conSeriesExts); err != nil { return err } } @@ -90,6 +105,10 @@ func (o Triples) MarshalCBOR() ([]byte, error) { o.EndorsedValues = nil } + if o.CondEndorseSeries != nil && o.CondEndorseSeries.IsEmpty() { + o.CondEndorseSeries = nil + } + return encoding.SerializeStructToCBOR(em, o) } @@ -114,6 +133,10 @@ func (o Triples) MarshalJSON() ([]byte, error) { o.EndorsedValues = nil } + if o.CondEndorseSeries != nil && o.CondEndorseSeries.IsEmpty() { + o.CondEndorseSeries = nil + } + return encoding.SerializeStructToJSON(o) } @@ -123,7 +146,8 @@ func (o Triples) Valid() error { if (o.ReferenceValues == nil || o.ReferenceValues.IsEmpty()) && (o.EndorsedValues == nil || o.EndorsedValues.IsEmpty()) && (o.AttestVerifKeys == nil || len(*o.AttestVerifKeys) == 0) && - (o.DevIdentityKeys == nil || len(*o.DevIdentityKeys) == 0) { + (o.DevIdentityKeys == nil || len(*o.DevIdentityKeys) == 0) && + (o.CondEndorseSeries == nil || o.CondEndorseSeries.IsEmpty()) { return fmt.Errorf("triples struct must not be empty") } @@ -155,44 +179,63 @@ func (o Triples) Valid() error { } } + if o.CondEndorseSeries != nil { + if err := o.CondEndorseSeries.Valid(); err != nil { + return fmt.Errorf("conditional series: %w", err) + } + } + return o.Extensions.validTriples(&o) } -func (o *Triples) AddReferenceValue(val ValueTriple) *Triples { +func (o *Triples) AddReferenceValue(val *ValueTriple) *Triples { if o != nil { if o.ReferenceValues == nil { o.ReferenceValues = new(ValueTriples) } - o.ReferenceValues.Add(&val) + o.ReferenceValues.Add(val) } return o } -func (o *Triples) AddEndorsedValue(val ValueTriple) *Triples { +func (o *Triples) AddEndorsedValue(val *ValueTriple) *Triples { if o != nil { if o.EndorsedValues == nil { o.EndorsedValues = new(ValueTriples) } - o.EndorsedValues.Add(&val) + o.EndorsedValues.Add(val) } return o } -func (o *Triples) AddAttestVerifKey(val KeyTriple) *Triples { +func (o *Triples) AddAttestVerifKey(val *KeyTriple) *Triples { if o != nil { - *o.AttestVerifKeys = append(*o.AttestVerifKeys, val) + *o.AttestVerifKeys = append(*o.AttestVerifKeys, *val) } return o } -func (o *Triples) AddDevIdentityKey(val KeyTriple) *Triples { +func (o *Triples) AddDevIdentityKey(val *KeyTriple) *Triples { if o != nil { - *o.DevIdentityKeys = append(*o.DevIdentityKeys, val) + *o.DevIdentityKeys = append(*o.DevIdentityKeys, *val) + } + + return o +} + +// nolint:gocritic +func (o *Triples) AddCondEndorseSeries(val *CondEndorseSeriesTriple) *Triples { + if o != nil { + if o.CondEndorseSeries == nil { + o.CondEndorseSeries = new(CondEndorseSeriesTriples) + } + + o.CondEndorseSeries.Add(val) } return o diff --git a/comid/triples_test.go b/comid/triples_test.go index 62e21e21..1c90c692 100644 --- a/comid/triples_test.go +++ b/comid/triples_test.go @@ -20,7 +20,9 @@ func TestTriples_extensions(t *testing.T) { Add(ExtReferenceValue, &struct{}{}). Add(ExtReferenceValueFlags, &struct{}{}). Add(ExtEndorsedValue, &struct{}{}). - Add(ExtEndorsedValueFlags, &struct{}{}) + Add(ExtEndorsedValueFlags, &struct{}{}). + Add(ExtCondEndorseSeriesValue, &struct{}{}). + Add(ExtCondEndorseSeriesValueFlags, &struct{}{}) err := triples.RegisterExtensions(extMap) assert.NoError(t, err) @@ -36,7 +38,8 @@ func TestTriples_marshaling(t *testing.T) { extMap := extensions.NewMap(). Add(ExtReferenceValue, &struct{}{}). - Add(ExtEndorsedValue, &struct{}{}) + Add(ExtEndorsedValue, &struct{}{}). + Add(ExtCondEndorseSeriesValue, &struct{}{}) require.NoError(t, triples.RegisterExtensions(extMap)) @@ -47,6 +50,7 @@ func TestTriples_marshaling(t *testing.T) { data, err = triples.MarshalJSON() assert.NoError(t, err) assert.JSONEq(t, "{}", string(data)) + } func TestTriples_Valid(t *testing.T) { @@ -75,12 +79,18 @@ func TestTriples_Valid(t *testing.T) { triples.DevIdentityKeys = &KeyTriples{{}} err = triples.Valid() assert.EqualError(t, err, "device identity key at index 0: environment validation failed: environment must not be empty") + + triples.DevIdentityKeys = nil + triples.CondEndorseSeries = &CondEndorseSeriesTriples{} + triples.CondEndorseSeries.Add(&CondEndorseSeriesTriple{}) + err = triples.Valid() + assert.EqualError(t, err, "conditional series: error at index 0: stateful environment validation failed: environment validation failed: environment must not be empty") } func TestTriples_adders(t *testing.T) { triples := Triples{} - triples.AddReferenceValue(ValueTriple{}).AddEndorsedValue(ValueTriple{}) + triples.AddReferenceValue(&ValueTriple{}).AddEndorsedValue(&ValueTriple{}) assert.Len(t, triples.ReferenceValues.Values, 1) assert.Len(t, triples.EndorsedValues.Values, 1) } diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index 956b3248..89cf7242 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -179,7 +179,7 @@ func Example_profile_marshal() { } refVal.Measurements.Add(measurement) - myComid.Triples.AddReferenceValue(refVal) + myComid.Triples.AddReferenceValue(&refVal) err = myComid.Valid() if err != nil { diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 1857f003..e0675e50 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -175,7 +175,7 @@ func TestUnsignedCorim_Valid_ok(t *testing.T) { c := comid.NewComid(). SetTagIdentity("vendor.example/prod/1", 0). AddAttestVerifKey( - comid.KeyTriple{ + &comid.KeyTriple{ Environment: comid.Environment{ Instance: comid.MustNewUUIDInstance(comid.TestUUID), }, @@ -309,7 +309,7 @@ func TestUnsignedCorim_ToJSON(t *testing.T) { c := comid.NewComid(). SetTagIdentity("vendor.example/prod/1", 0). AddAttestVerifKey( - comid.KeyTriple{ + &comid.KeyTriple{ Environment: comid.Environment{ Instance: comid.MustNewUUIDInstance(comid.TestUUID), }, @@ -352,7 +352,7 @@ func TestUnsignedCorim_ToCBOR(t *testing.T) { c := comid.NewComid(). SetTagIdentity("vendor.example/prod/1", 0). AddAttestVerifKey( - comid.KeyTriple{ + &comid.KeyTriple{ Environment: comid.Environment{ Instance: comid.MustNewUUIDInstance(comid.TestUUID), }, diff --git a/extensions/collection.go b/extensions/collection.go index 28a7099a..32d2187a 100644 --- a/extensions/collection.go +++ b/extensions/collection.go @@ -101,7 +101,7 @@ func (o *Collection[P, I]) GetExtensions() IMapValue { return o.valueExtensions.Get() } -// Valid returns an error if the collection is invalid, i.e. if it is empty or +// Valid returns an error if the collection is invalid, i.e. // if any of its contents are invalid. func (o Collection[P, I]) Valid() error { for i, p := range o.Values { From 34ab2cc6469bb0ed016d5ae91fad56a16b04fa7d Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Thu, 13 Mar 2025 17:06:08 +0000 Subject: [PATCH 091/110] Apply suggestions from code review Comments addressed! Co-authored-by: setrofim Signed-off-by: Yogesh Deshpande --- comid/cond_endorse_series_triple.go | 4 ++-- comid/triples_test.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/comid/cond_endorse_series_triple.go b/comid/cond_endorse_series_triple.go index d54d768b..4ae88817 100644 --- a/comid/cond_endorse_series_triple.go +++ b/comid/cond_endorse_series_triple.go @@ -12,7 +12,7 @@ import ( // A Stateful Environment is an Environment in a known reference state type StatefulEnv = ValueTriple -// A Conditional Series Record, has a series of conditions identified by +// A Conditional Endorsement Series Record, has a series of conditions identified by // the selection which are matched with the Attester Actual State(from Evidence) // First successful match terminates matching and corresponding addition are added // as Endorsements @@ -77,7 +77,7 @@ func (o *CondEndorseSeriesRecords) Valid() error { } // The Conditional Endorsement Series Triple is used to assert endorsed values based -// on an initial condition match (specified in condition:) followed by a series +// on an initial condition match (specified by Condition StatefulEnv) followed by a series // condition match (specified in selection: inside conditional-series-record). type CondEndorseSeriesTriple struct { _ struct{} `cbor:",toarray"` diff --git a/comid/triples_test.go b/comid/triples_test.go index 1c90c692..8d5b1544 100644 --- a/comid/triples_test.go +++ b/comid/triples_test.go @@ -50,7 +50,6 @@ func TestTriples_marshaling(t *testing.T) { data, err = triples.MarshalJSON() assert.NoError(t, err) assert.JSONEq(t, "{}", string(data)) - } func TestTriples_Valid(t *testing.T) { From 48d444e1c09625750ef504b852903d2e85717dba Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Tue, 18 Mar 2025 14:34:49 +0000 Subject: [PATCH 092/110] Incorporating review changes Signed-off-by: Yogesh Deshpande --- comid/testcases/regen-from-src.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 comid/testcases/regen-from-src.sh diff --git a/comid/testcases/regen-from-src.sh b/comid/testcases/regen-from-src.sh old mode 100755 new mode 100644 From 5950df10fe752cc788409b62d8b94c700d65e4ee Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Tue, 18 Mar 2025 14:36:06 +0000 Subject: [PATCH 093/110] Update comid/cond_endorse_series_triple.go Co-authored-by: Thomas Fossati --- comid/cond_endorse_series_triple.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comid/cond_endorse_series_triple.go b/comid/cond_endorse_series_triple.go index 4ae88817..4d8b7b72 100644 --- a/comid/cond_endorse_series_triple.go +++ b/comid/cond_endorse_series_triple.go @@ -23,7 +23,7 @@ type CondEndorseSeriesRecord struct { func (o CondEndorseSeriesRecord) Valid() error { if err := o.Selection.Valid(); err != nil { - return fmt.Errorf("conditional series record selection validation failed: %w", err) + return fmt.Errorf("selection validation failed: %w", err) } if err := o.Addition.Valid(); err != nil { From 51737fa0dd2e70cbd62fd74b5aa33813737bd49c Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Tue, 18 Mar 2025 18:10:00 +0100 Subject: [PATCH 094/110] remove superflous diagnostic --- comid/cond_endorse_series_triple.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comid/cond_endorse_series_triple.go b/comid/cond_endorse_series_triple.go index 4d8b7b72..bff08712 100644 --- a/comid/cond_endorse_series_triple.go +++ b/comid/cond_endorse_series_triple.go @@ -27,7 +27,7 @@ func (o CondEndorseSeriesRecord) Valid() error { } if err := o.Addition.Valid(); err != nil { - return fmt.Errorf("conditional series record addition validation failed: %w", err) + return fmt.Errorf("addition validation failed: %w", err) } return nil } From c72125652c527f07cdf3ae23ce12a20666327d04 Mon Sep 17 00:00:00 2001 From: Dionna Amalie Glaze Date: Tue, 18 Mar 2025 10:44:36 -0700 Subject: [PATCH 095/110] Add Name measurement value (#139) * Add Name measurement value Fixes #56 Signed-off-by: Dionna Glaze * Fix format error Signed-off-by: Yogesh Deshpande * Fix format Signed-off-by: Yogesh Deshpande * Fix Format Signed-off-by: Yogesh Deshpande --------- Signed-off-by: Dionna Glaze Signed-off-by: Yogesh Deshpande Co-authored-by: Yogesh Deshpande --- comid/measurement.go | 12 ++++++++++++ comid/measurement_test.go | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/comid/measurement.go b/comid/measurement.go index 98549747..9ff21627 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -353,6 +353,7 @@ type Mval struct { SerialNumber *string `cbor:"8,keyasint,omitempty" json:"serial-number,omitempty"` UEID *eat.UEID `cbor:"9,keyasint,omitempty" json:"ueid,omitempty"` UUID *UUID `cbor:"10,keyasint,omitempty" json:"uuid,omitempty"` + Name *string `cbor:"11,keyasint,omitempty" json:"name,omitempty"` IntegrityRegisters *IntegrityRegisters `cbor:"14,keyasint,omitempty" json:"integrity-registers,omitempty"` Extensions } @@ -439,8 +440,10 @@ func (o Mval) Valid() error { o.SerialNumber == nil && o.UEID == nil && o.UUID == nil && + o.Name == nil && o.IntegrityRegisters == nil && o.Extensions.IsEmpty() { + return fmt.Errorf("no measurement value set") } @@ -756,6 +759,15 @@ func (o *Measurement) SetUUID(u UUID) *Measurement { return o } +// SetName sets the supplied name string in the measurement-values-map of the +// target measurement +func (o *Measurement) SetName(name string) *Measurement { + if o != nil { + o.Val.Name = &name + } + return o +} + // nolint:gocritic func (o Measurement) Valid() error { if o.Key != nil && o.Key.IsSet() { diff --git a/comid/measurement_test.go b/comid/measurement_test.go index 45505ea2..822d80f0 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -193,6 +193,12 @@ func TestMeasurement_NewUUIDMeasurement_bad_uuid(t *testing.T) { assert.Nil(t, tv.SetUUID(nonRFC4122UUID)) } +func TestMeasurement_NameMeasurement(t *testing.T) { + want := "Maureen" + got := *(&Measurement{}).SetName("Maureen").Val.Name + assert.Equal(t, want, got) +} + var ( testMKeyUintMin uint64 testMKeyUintMax = ^uint64(0) From 9c8d9d75bbf6283c7797d3a5a016fd3d697daf84 Mon Sep 17 00:00:00 2001 From: Jag Raman Date: Fri, 2 May 2025 17:45:45 -0400 Subject: [PATCH 096/110] rawint: add a raw int concrete type (#183) * rawint: add a raw int concrete type Add a raw int concrete type as mentioned in the IETF CoRIM spec: https://ietf-rats-wg.github.io/draft-ietf-rats-corim/draft-ietf-rats-corim.html#name-raw-int Reviewed-by: Dionna Amalie Glaze Reviewed-by: Yogesh Deshpande Signed-off-by: Jagannathan Raman --- comid/cbor.go | 1 + comid/rawint.go | 347 +++++++++++++++++++++++++++++++++++++++++++ comid/rawint_test.go | 203 +++++++++++++++++++++++++ 3 files changed, 551 insertions(+) create mode 100644 comid/rawint.go create mode 100644 comid/rawint_test.go diff --git a/comid/cbor.go b/comid/cbor.go index f7f044f9..9f6313ea 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -31,6 +31,7 @@ var ( 559: TaggedCertThumbprint{}, 560: TaggedBytes{}, 561: TaggedCertPathThumbprint{}, + 564: TaggedRawIntRange{}, // PSA profile tags 600: TaggedImplID{}, 601: TaggedPSARefValID{}, diff --git a/comid/rawint.go b/comid/rawint.go new file mode 100644 index 00000000..7eb9eaea --- /dev/null +++ b/comid/rawint.go @@ -0,0 +1,347 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "strconv" + + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" +) + +var ( + // ErrRawIntUnsetRawInt is the error returned when a RawInt value isn't set + ErrRawIntUnsetRawInt = errors.New("RawInt value unset") + + // ErrRawIntEmptyInput is the error returned when the input to a function is empty + ErrRawIntEmptyInput = errors.New("Empty input") +) + +// RawInt describes an integer value that can be compared with linear order in +// the target environment. It follows the type-choice pattern widely +// used in CoRIM and implements the extensions.ITypeChoiceValue interface +// https://ietf-rats-wg.github.io/draft-ietf-rats-corim/draft-ietf-rats-corim.html#name-raw-int +type RawInt struct { + Value extensions.ITypeChoiceValue +} + +// NewRawInt returns a *RawInt of the specified type +func NewRawInt(val any, typ string) (*RawInt, error) { + factory, ok := rawIntValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unknown type: %s", typ) + } + + return factory(val) +} + +// IsSet confirms if RawInt has a value or if it's empty +func (o RawInt) IsSet() bool { return o.Value != nil } + +// Type returns the type of RawInt +func (o RawInt) Type() string { + if o.IsSet() { + return o.Value.Type() + } + + return "" +} + +// Valid checks if the RawInt is valid +func (o RawInt) Valid() error { + if !o.IsSet() { + return ErrRawIntUnsetRawInt + } + + return o.Value.Valid() +} + +// String returns the RawInt in a string format +func (o RawInt) String() string { + if !o.IsSet() { + return "" + } + + return o.Value.String() +} + +// MarshalJSON serializes RawInt into JSON +func (o RawInt) MarshalJSON() ([]byte, error) { + valueBytes, err := json.Marshal(o.Value) + if err != nil { + return nil, err + } + + value := encoding.TypeAndValue{Type: o.Value.Type(), Value: valueBytes} + + return json.Marshal(value) +} + +// UnmarshalJSON de-serializes input JSON data into RawInt +func (o *RawInt) UnmarshalJSON(data []byte) error { + var tnv encoding.TypeAndValue + + if err := json.Unmarshal(data, &tnv); err != nil { + return err + } + + decoded, err := NewRawInt(nil, tnv.Type) + if err != nil { + return err + } + + if err := json.Unmarshal(tnv.Value, decoded.Value); err != nil { + return err + } + + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value + + return nil +} + +// MarshalCBOR serializes RawInt to CBOR +func (o RawInt) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.Value) +} + +// UnmarshalCBOR de-serializes input CBOR into RawInt +func (o *RawInt) UnmarshalCBOR(data []byte) error { + if len(data) == 0 { + return ErrRawIntEmptyInput + } + + majorType := (data[0] & 0xe0) >> 5 + switch majorType { + case 0, 1: + rawIntInteger := new(RawIntInteger) + if err := dm.Unmarshal(data, rawIntInteger); err != nil { + return err + } + o.Value = rawIntInteger + case 6: + rawIntRange := new(TaggedRawIntRange) + if err := dm.Unmarshal(data, rawIntRange); err != nil { + return err + } + o.Value = rawIntRange + default: + return fmt.Errorf("RawInt: unknown major type: %d", majorType) + } + + return nil +} + +// RawIntIntegerType is the name of the Integer version of RawInt +const RawIntIntegerType = "rawIntInteger" + +// RawIntInteger implements the Integer version of RawInt. It can +// hold both positive and negative integer values +type RawIntInteger int64 + +// NewRawIntInteger creates a RawIntInteger from the given input value +func NewRawIntInteger(val any) (*RawIntInteger, error) { + var ret RawIntInteger + + if val == nil { + return &ret, nil + } + + switch v := val.(type) { + case RawIntInteger: + ret = v + case *RawIntInteger: + ret = *v + case int64: + ret = RawIntInteger(v) + default: + return nil, fmt.Errorf("unexpected type for RawIntInteger: %T", v) + } + + return &ret, nil +} + +// Valid is a no-op for RawIntInteger and always returns nil +func (o RawIntInteger) Valid() error { return nil } + +// String converts RawIntInteger into a string +func (o RawIntInteger) String() string { return strconv.FormatInt(int64(o), 10) } + +// Type returns the name/type of RawIntInteger +func (o RawIntInteger) Type() string { + return RawIntIntegerType +} + +// CompareAgainstRefInteger compares RawIntInteger against another RawIntInteger +// reference value. The receiver is the claim and the input parameter is the +// reference value. Returns true if the claim is equal to the reference; false otherwise. +func (o RawIntInteger) CompareAgainstRefInteger(ref RawIntInteger) bool { + return o == ref +} + +// CompareAgainstRefRange compares RawIntInteger against a TaggedRawIntRange. +// The receiver is the claim and the input parameter is the reference value. +// Returns true if the claim is within the reference range; false otherwise. +func (o RawIntInteger) CompareAgainstRefRange(ref TaggedRawIntRange) bool { + obj, err := convertRawIntIntegerToInt64(o) + if err != nil { + log.Printf("RawIntInteger:CompareAgainstRefRange: Error: %v", err) + return false + } + + if ref.Min != nil && obj < *ref.Min { + return false + } + + if ref.Max != nil && obj > *ref.Max { + return false + } + + return true +} + +func convertRawIntIntegerToInt64(val any) (int64, error) { + switch v := val.(type) { + case RawIntInteger: + return int64(v), nil + case *RawIntInteger: + return int64(*v), nil + default: + return 0, fmt.Errorf("unexpected type for RawIntInteger: %T", v) + } +} + +// TaggedRawIntRangeType is the name of the Range version of RawInt +const TaggedRawIntRangeType = "rawIntRange" + +// TaggedRawIntRange implements the Range version of RawInt. The range is +// made of minimum and maximum values. If the minimum is nil, it's assumed +// to be negative infinity. If the maximum is nil, it's assumed to be +// positive infinity. +type TaggedRawIntRange struct { + Min *int64 + Max *int64 +} + +// NewRawIntRange creates a TaggedRawIntRange with the input value +func NewRawIntRange(val any) (*TaggedRawIntRange, error) { + var ret TaggedRawIntRange + + if val == nil { + return &ret, nil + } + + switch v := val.(type) { + case TaggedRawIntRange: + ret = v + case *TaggedRawIntRange: + ret = *v + default: + return nil, fmt.Errorf("unexpected type for TaggedRawIntRange: %T", v) + } + + return &ret, nil +} + +// Valid checks if TaggedRawIntRange is valid +func (o TaggedRawIntRange) Valid() error { + if o.Min != nil && o.Max != nil && *o.Min > *o.Max { + return fmt.Errorf("TaggedRawIntRange: Invalid Range, Min: %d Max: %d", *o.Min, *o.Max) + } + + return nil +} + +// String converts TaggedRawIntRange to a string +func (o TaggedRawIntRange) String() string { + var rangeMin, rangeMax string + + if o.Min != nil { + rangeMin = fmt.Sprintf("[%d", *o.Min) + } else { + rangeMin = "(-inf" + } + + if o.Max != nil { + rangeMax = fmt.Sprintf("%d]", *o.Max) + } else { + rangeMax = "inf)" + } + + return fmt.Sprintf("%s:%s", rangeMin, rangeMax) +} + +// Type returns the name/type of TaggedRawIntRange +func (o TaggedRawIntRange) Type() string { + return TaggedRawIntRangeType +} + +// CompareAgainstRefInteger compares TaggedRawIntRange against a given +// RawIntInteger reference. The receiver is the claim and the input +// parameter is the reference value. Returns true if and only if the +// claim is equal to both reference min & max values; false otherwise. +func (o TaggedRawIntRange) CompareAgainstRefInteger(ref RawIntInteger) bool { + refVal, err := convertRawIntIntegerToInt64(ref) + if err != nil { + log.Printf("TaggedRawIntRange:CompareAgainstRefInteger: Error: %v", err) + return false + } + + if o.Min != nil && refVal == *o.Min && o.Max != nil && refVal == *o.Max { + return true + } + + return false +} + +// CompareAgainstRefRange compares TaggedRawIntRange against another +// TaggedRawIntRange reference. The receiver is the claim and the input +// parameter is the reference value. Returns true if the range claim is a +// subset-range of the reference range; false otherwise. +func (o TaggedRawIntRange) CompareAgainstRefRange(ref TaggedRawIntRange) bool { + if o.Min != nil && ref.Min != nil && *o.Min < *ref.Min { + return false + } + + if o.Max != nil && ref.Max != nil && *o.Max > *ref.Max { + return false + } + + return true +} + +// NewRawIntIntegerType returns a new RawIntInteger from given value +func NewRawIntIntegerType(val any) (*RawInt, error) { + ret, err := NewRawIntInteger(val) + if err != nil { + return nil, err + } + + return &RawInt{Value: ret}, nil +} + +// NewRawIntRangeType returns a new TaggedRawIntRange from given value +func NewRawIntRangeType(val any) (*RawInt, error) { + ret, err := NewRawIntRange(val) + if err != nil { + return nil, err + } + + return &RawInt{Value: ret}, nil +} + +// IRawIntFactory type defines a factory pattern for RawInt +type IRawIntFactory = func(val any) (*RawInt, error) + +var rawIntValueRegister = map[string]IRawIntFactory{ + RawIntIntegerType: NewRawIntIntegerType, + TaggedRawIntRangeType: NewRawIntRangeType, +} diff --git a/comid/rawint_test.go b/comid/rawint_test.go new file mode 100644 index 00000000..d19a9631 --- /dev/null +++ b/comid/rawint_test.go @@ -0,0 +1,203 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package comid + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRawInt_NewRawIntInteger_OK(t *testing.T) { + var val int64 = -25 + rawInt, err := NewRawInt(val, "rawIntInteger") + assert.NoError(t, err) + assert.NoError(t, rawInt.Valid()) +} + +func TestRawInt_NewRawIntRange_OK(t *testing.T) { + minVal := int64(-25) + maxVal := int64(-25) + val := TaggedRawIntRange{Max: &maxVal, Min: &minVal} + + rawInt, err := NewRawInt(val, "rawIntRange") + assert.NoError(t, err) + assert.NoError(t, rawInt.Valid()) +} + +func TestRawInt_NewRawIntRange_Validity(t *testing.T) { + minVal := int64(-25) + maxVal := int64(-35) + val := TaggedRawIntRange{Max: &maxVal, Min: &minVal} + + rawInt, err := NewRawInt(val, "rawIntRange") + assert.NoError(t, err) + assert.EqualError(t, rawInt.Valid(), fmt.Sprintf("TaggedRawIntRange: Invalid Range, Min: %d Max: %d", minVal, maxVal)) +} + +func TestRawInt_NewRawIntInteger_MarshalUnmarshalJSON(t *testing.T) { + var val int64 = -65 + + rawInt, err := NewRawInt(val, "rawIntInteger") + assert.NoError(t, err) + + json, err := rawInt.MarshalJSON() + assert.NoError(t, err) + + unmarshaledRawInt := &RawInt{} + err = unmarshaledRawInt.UnmarshalJSON(json) + assert.NoError(t, err) + + rawIntInteger, ok := unmarshaledRawInt.Value.(*RawIntInteger) + assert.True(t, ok) + assert.Equal(t, fmt.Sprintf("%d", val), rawIntInteger.String()) +} + +func TestRawInt_NewRawIntInteger_MarshalUnmarshalCBOR(t *testing.T) { + var val int64 = -85 + + rawInt, err := NewRawInt(val, "rawIntInteger") + assert.NoError(t, err) + + rawIntCbor, err := rawInt.MarshalCBOR() + assert.NoError(t, err) + + unmarshaledRawInt := &RawInt{} + err = unmarshaledRawInt.UnmarshalCBOR(rawIntCbor) + assert.NoError(t, err) + + rawIntInteger, ok := unmarshaledRawInt.Value.(*RawIntInteger) + assert.True(t, ok) + assert.Equal(t, fmt.Sprintf("%d", val), rawIntInteger.String()) +} + +func TestRawInt_NewRawIntRange_MarshalUnmarshalJSON(t *testing.T) { + minVal := int64(-25) + + rawInt, err := NewRawInt(TaggedRawIntRange{Max: nil, Min: &minVal}, "rawIntRange") + assert.NoError(t, err) + + rawIntJSON, err := rawInt.MarshalJSON() + assert.NoError(t, err) + + unmarshaledRawInt := &RawInt{} + err = unmarshaledRawInt.UnmarshalJSON(rawIntJSON) + assert.NoError(t, err) + + rawIntRange, ok := unmarshaledRawInt.Value.(*TaggedRawIntRange) + assert.True(t, ok) + assert.Equal(t, fmt.Sprintf("[%d:inf)", minVal), rawIntRange.String()) +} + +func TestRawInt_NewRawIntRange_MarshalUnmarshalCBOR(t *testing.T) { + maxVal := int64(650) + + rawInt, err := NewRawInt(TaggedRawIntRange{Max: &maxVal, Min: nil}, "rawIntRange") + assert.NoError(t, err) + + rawIntCBOR, err := rawInt.MarshalCBOR() + assert.NoError(t, err) + + unmarshaledRawInt := &RawInt{} + err = unmarshaledRawInt.UnmarshalCBOR(rawIntCBOR) + assert.NoError(t, err) + + rawIntRange, ok := unmarshaledRawInt.Value.(*TaggedRawIntRange) + assert.True(t, ok) + assert.Equal(t, fmt.Sprintf("(-inf:%d]", maxVal), rawIntRange.String()) +} + +func TestRawInt_Compare_IntegerClaimVsIntegerRef_Pass(t *testing.T) { + claim := RawIntInteger(65) + ref := RawIntInteger(65) + + assert.True(t, claim.CompareAgainstRefInteger(ref)) +} + +func TestRawInt_Compare_IntegerClaimVsIntegerRef_Fail(t *testing.T) { + claim := RawIntInteger(65) + ref := RawIntInteger(75) + + assert.False(t, claim.CompareAgainstRefInteger(ref)) +} + +func TestRawInt_Compare_IntegerClaimVsRangeRef_Pass(t *testing.T) { + claim := RawIntInteger(65) + refMinVal := int64(-25) + refMaxVal := int64(75) + ref := TaggedRawIntRange{Min: &refMinVal, Max: &refMaxVal} + + assert.True(t, claim.CompareAgainstRefRange(ref)) +} + +func TestRawInt_Compare_IntegerClaimVsRangeRef_Fail(t *testing.T) { + claim := RawIntInteger(85) + refMinVal := int64(-25) + refMaxVal := int64(75) + ref := TaggedRawIntRange{Min: &refMinVal, Max: &refMaxVal} + + assert.False(t, claim.CompareAgainstRefRange(ref)) +} + +func TestRawInt_Compare_RangeClaimVsIntegerRef_Pass(t *testing.T) { + claimMinVal := int64(25) + claimMaxVal := int64(25) + claim := TaggedRawIntRange{Min: &claimMinVal, Max: &claimMaxVal} + ref := RawIntInteger(25) + + assert.True(t, claim.CompareAgainstRefInteger(ref)) +} + +func TestRawInt_Compare_RangeClaimVsIntegerRef_Fail(t *testing.T) { + claimMaxVal := int64(25) + claim := TaggedRawIntRange{Min: nil, Max: &claimMaxVal} + ref := RawIntInteger(25) + + assert.False(t, claim.CompareAgainstRefInteger(ref)) +} + +func TestRawInt_Compare_RangeClaimVsRangeRef_Pass(t *testing.T) { + refMinVal := int64(-100) + refMaxVal := int64(100) + ref := TaggedRawIntRange{Min: &refMinVal, Max: &refMaxVal} + + claimMinVal := int64(-25) + claimMaxVal := int64(75) + claim := TaggedRawIntRange{Min: &claimMinVal, Max: &claimMaxVal} + + assert.True(t, claim.CompareAgainstRefRange(ref)) +} + +func TestRawInt_Compare_RangeClaimVsRangeRef_Pass_RefMinInf(t *testing.T) { + refMaxVal := int64(100) + ref := TaggedRawIntRange{Min: nil, Max: &refMaxVal} + + claimMinVal := int64(-25) + claimMaxVal := int64(75) + claim := TaggedRawIntRange{Min: &claimMinVal, Max: &claimMaxVal} + + assert.True(t, claim.CompareAgainstRefRange(ref)) +} + +func TestRawInt_Compare_RangeClaimVsRangeRef_Pass_RefMinInf_ClaimMinInf(t *testing.T) { + refMaxVal := int64(100) + ref := TaggedRawIntRange{Min: nil, Max: &refMaxVal} + + claimMaxVal := int64(75) + claim := TaggedRawIntRange{Min: nil, Max: &claimMaxVal} + + assert.True(t, claim.CompareAgainstRefRange(ref)) +} + +func TestRawInt_Compare_RangeClaimVsRangeRef_Fail(t *testing.T) { + refMinVal := int64(-100) + refMaxVal := int64(100) + ref := TaggedRawIntRange{Min: &refMinVal, Max: &refMaxVal} + + claimMinVal := int64(-101) + claimMaxVal := int64(75) + claim := TaggedRawIntRange{Min: &claimMinVal, Max: &claimMaxVal} + + assert.False(t, claim.CompareAgainstRefRange(ref)) +} From 0a071af0b420d18512b81e65a0749a5f3a706b2f Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Wed, 14 May 2025 14:10:12 +0100 Subject: [PATCH 097/110] feat: Enhance TDX profile with expression semantics (#181) feat: Enhance TDX profile with expression semantics Complies with draft 04 of TDX profile Signed-off-by: Yogesh Deshpande --- comid/example_psa_refval_test.go | 4 +- comid/tdx-profile/cbor.go | 64 +++ comid/tdx-profile/common_extract_test.go | 162 ++++++ comid/tdx-profile/example_pce_refval_test.go | 160 +++--- comid/tdx-profile/example_qe_refval_test.go | 104 ++-- comid/tdx-profile/example_seam_refval_test.go | 207 +++---- comid/tdx-profile/mval_extensions.go | 26 +- comid/tdx-profile/numeric_expression.go | 41 ++ comid/tdx-profile/numeric_type.go | 186 +++++++ comid/tdx-profile/numeric_type_test.go | 92 ++++ comid/tdx-profile/operator.go | 68 +++ comid/tdx-profile/set_expression.go | 62 +++ comid/tdx-profile/tee_tcb_eval_num.go | 133 +++++ comid/tdx-profile/tee_tcb_eval_num_test.go | 87 +++ comid/tdx-profile/teeadvisoryids.go | 170 ++++-- comid/tdx-profile/teeadvisoryids_test.go | 109 ++-- comid/tdx-profile/teedigest.go | 188 +++++++ comid/tdx-profile/teedigest_test.go | 61 +++ comid/tdx-profile/teeinstanceid.go | 9 +- comid/tdx-profile/teeisvproid.go | 13 +- comid/tdx-profile/teesvn.go | 141 +++++ comid/tdx-profile/teetcbcompsvn.go | 35 +- comid/tdx-profile/teetcbcompsvn_test.go | 61 +++ comid/tdx-profile/teetcbstatus.go | 155 +++++- comid/tdx-profile/teetcbstatus_test.go | 70 ++- comid/tdx-profile/test_vars.go | 512 ++++++++++++------ .../testcases/comid_pce_refval.cbor | Bin 382 -> 462 bytes .../testcases/comid_qe_refval.cbor | Bin 432 -> 497 bytes .../testcases/comid_seam_refval.cbor | Bin 456 -> 476 bytes .../testcases/src/comid_pce_refval.diag | 19 +- .../testcases/src/comid_qe_refval.diag | 26 +- .../testcases/src/comid_seam_refval.diag | 16 +- comid/tdx-profile/types.go | 18 +- 33 files changed, 2416 insertions(+), 583 deletions(-) create mode 100644 comid/tdx-profile/cbor.go create mode 100644 comid/tdx-profile/common_extract_test.go create mode 100644 comid/tdx-profile/numeric_expression.go create mode 100644 comid/tdx-profile/numeric_type.go create mode 100644 comid/tdx-profile/numeric_type_test.go create mode 100644 comid/tdx-profile/operator.go create mode 100644 comid/tdx-profile/set_expression.go create mode 100644 comid/tdx-profile/tee_tcb_eval_num.go create mode 100644 comid/tdx-profile/tee_tcb_eval_num_test.go create mode 100644 comid/tdx-profile/teedigest.go create mode 100644 comid/tdx-profile/teedigest_test.go create mode 100644 comid/tdx-profile/teesvn.go create mode 100644 comid/tdx-profile/teetcbcompsvn_test.go diff --git a/comid/example_psa_refval_test.go b/comid/example_psa_refval_test.go index 3634736e..4181c3d5 100644 --- a/comid/example_psa_refval_test.go +++ b/comid/example_psa_refval_test.go @@ -83,14 +83,14 @@ func extractSwMeasurement(m *Measurement) error { return fmt.Errorf("extracting PSA refval id: %w", err) } - if err := extractDigest(m.Val.Digests); err != nil { + if err := testextractDigest(m.Val.Digests); err != nil { return fmt.Errorf("extracting digest: %w", err) } return nil } -func extractDigest(d *Digests) error { +func testextractDigest(d *Digests) error { if d == nil { return fmt.Errorf("no digest") } diff --git a/comid/tdx-profile/cbor.go b/comid/tdx-profile/cbor.go new file mode 100644 index 00000000..c850fbd5 --- /dev/null +++ b/comid/tdx-profile/cbor.go @@ -0,0 +1,64 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "reflect" + + cbor "github.com/fxamacker/cbor/v2" +) + +var ( + em, emError = initCBOREncMode() + dm, dmError = initCBORDecMode() + + tdxTagsMap = map[uint64]interface{}{ + // TDX tags + 60010: TaggedNumericExpression{}, + 60020: TaggedSetDigestExpression{}, + 60021: TaggedSetStringExpression{}, + } +) + +func tdxTags() cbor.TagSet { + opts := cbor.TagOptions{ + EncTag: cbor.EncTagRequired, + DecTag: cbor.DecTagRequired, + } + + tags := cbor.NewTagSet() + + for tag, typ := range tdxTagsMap { + if err := tags.Add(opts, reflect.TypeOf(typ), tag); err != nil { + panic(err) + } + } + + return tags +} + +func initCBOREncMode() (en cbor.EncMode, err error) { + encOpt := cbor.EncOptions{ + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + TimeTag: cbor.EncTagRequired, + } + return encOpt.EncModeWithTags(tdxTags()) +} + +func initCBORDecMode() (dm cbor.DecMode, err error) { + decOpt := cbor.DecOptions{ + IndefLength: cbor.IndefLengthForbidden, + } + return decOpt.DecModeWithTags(tdxTags()) +} + +func init() { + if emError != nil { + panic(emError) + } + if dmError != nil { + panic(dmError) + } +} diff --git a/comid/tdx-profile/common_extract_test.go b/comid/tdx-profile/common_extract_test.go new file mode 100644 index 00000000..b4c6d315 --- /dev/null +++ b/comid/tdx-profile/common_extract_test.go @@ -0,0 +1,162 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "fmt" + + "github.com/veraison/corim/comid" +) + +func testextractClassElements(c *comid.Class) error { + if c == nil { + return fmt.Errorf("no class") + } + + classID := c.ClassID + + if classID == nil { + return fmt.Errorf("no class-id") + } + + if classID.Type() != comid.OIDType { + return fmt.Errorf("class id is not an oid") + } + + fmt.Printf("OID: %s", classID.Value.String()) + + if c.Vendor == nil { + return fmt.Errorf("no Vendor") + } + fmt.Printf("\nVendor: %s", *c.Vendor) + + if c.Model == nil { + return fmt.Errorf("no Model") + } + fmt.Printf("\nModel: %s", *c.Model) + + return nil +} + +func testextractDigest(typ string, d *Digests) error { + if d == nil { + return fmt.Errorf("no digest") + } + + for _, digest := range *d { + fmt.Printf("\n%s Digest Alg: %d", typ, digest.HashAlgID) + fmt.Printf("\n%s Digest Value: %x", typ, digest.HashValue) + } + + return nil +} + +func testextractTeeDigest(typ string, d *TeeDigest) error { + if d == nil { + return fmt.Errorf("no TEE digest") + } + + if typ != "mrsigner" && typ != "mrtee" { + return fmt.Errorf("invalid type for TEE digest: %s", typ) + } + + if d.IsDigests() { + dg, err := d.GetDigest() + if err != nil { + return fmt.Errorf("unable to extract TEE Digest: %w", err) + } + err = testextractDigest(typ, &dg) + if err != nil { + return fmt.Errorf("unable to extract %s Digest: %w", typ, err) + } + } else { + de, err := d.GetDigestExpr() + if err != nil { + return fmt.Errorf("unable to extract TEE Digest Expression: %w", err) + } + fmt.Printf("\n%s Digest Operator: %s", typ, NumericOperatorToString[de.SetOperator]) + dg := comid.Digests(de.SetDigest) + err = testextractDigest(typ, &dg) + if err != nil { + return fmt.Errorf("unable to extract %s Digest: %w", typ, err) + } + } + return nil +} + +func testextractTeeSvn(teesvn *TeeSVN) error { + if teesvn == nil { + return fmt.Errorf("teesvn is nil") + } + if teesvn.IsUint() { + svn, err := teesvn.GetUint() + if err != nil { + return fmt.Errorf("unable to get Uint SVN at index: %w", err) + } + fmt.Printf("\nISVSVN: %d", svn) + } else if teesvn.IsExpression() { + svn, err := teesvn.GetNumericExpression() + if err != nil { + return fmt.Errorf("unable to get SVN Expression: %w", err) + } + fmt.Printf("\nSVN Operator: %s", NumericOperatorToString[svn.NumericOperator]) + fmt.Printf("\nSVN Value: %d", svn.NumericType.val) + } else { + return fmt.Errorf("teesvn, is neither uint or numeric") + } + return nil +} + +func testextractTeeISVProdID(isvprodID *TeeISVProdID) error { + if isvprodID == nil { + return fmt.Errorf("isvprodID is nil") + } + + if isvprodID.IsBytes() { + val, err := isvprodID.GetBytes() + if err != nil { + return fmt.Errorf("failed to decode isvprodid: %w", err) + } + fmt.Printf("\nIsvProdID: %x", val) + } else if isvprodID.IsUint() { + val, err := isvprodID.GetUint() + if err != nil { + return fmt.Errorf("failed to decode isvprodid: %w", err) + } + fmt.Printf("\nIsvProdID: %d", val) + } else { + return fmt.Errorf("isvprodid is neither integer or byte string") + } + return nil +} + +func testextractTeeTcbEvalNum(tcbEvalNum *TeeTcbEvalNumber) error { + if tcbEvalNum == nil { + return fmt.Errorf("tcbevalnum is nil") + } + if tcbEvalNum.IsExpression() { + ne, err1 := tcbEvalNum.GetNumericExpression() + if err1 != nil { + return fmt.Errorf("failed to get tcbEvalNum numeric expression: %w", err1) + } + fmt.Printf("\ntcbEvalNum Operator: %s", NumericOperatorToString[ne.NumericOperator]) + fmt.Printf("\ntcbEvalNum Value: %d", ne.NumericType.val) + } else if tcbEvalNum.IsUint() { + nv, err1 := tcbEvalNum.GetUint() + if err1 != nil { + return fmt.Errorf("failed to get tcbEvalNum uint: %w", err1) + } + fmt.Printf("\ntcbEvalNum: %d", nv) + } + return nil +} + +func TestdecodeAuthorisedBy(m *comid.Measurement) error { + if err := m.AuthorizedBy.Valid(); err != nil { + return fmt.Errorf("invalid cryptokey: %w", err) + } + fmt.Printf("\nCryptoKey Type: %s", m.AuthorizedBy.Type()) + fmt.Printf("\nCryptoKey Value: %s", m.AuthorizedBy.String()) + return nil +} diff --git a/comid/tdx-profile/example_pce_refval_test.go b/comid/tdx-profile/example_pce_refval_test.go index d8dd1472..5d52c6e0 100644 --- a/comid/tdx-profile/example_pce_refval_test.go +++ b/comid/tdx-profile/example_pce_refval_test.go @@ -5,6 +5,7 @@ package tdx import ( _ "embed" + "errors" "fmt" "github.com/veraison/corim/comid" @@ -43,22 +44,38 @@ func Example_decode_PCE_JSON() { // Model: 0123456789ABCDEF // InstanceID: 11 // pceID: 0000 - // SVN[0]: 10 - // SVN[1]: 10 - // SVN[2]: 2 - // SVN[3]: 2 - // SVN[4]: 2 - // SVN[5]: 1 - // SVN[6]: 4 - // SVN[7]: 0 - // SVN[8]: 0 - // SVN[9]: 0 - // SVN[10]: 0 - // SVN[11]: 0 - // SVN[12]: 0 - // SVN[13]: 0 - // SVN[14]: 0 - // SVN[15]: 0 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 2 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 // CryptoKey Type: pkix-base64-key // CryptoKey Value: -----BEGIN PUBLIC KEY----- // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== @@ -67,7 +84,7 @@ func Example_decode_PCE_JSON() { func extractPCERefVals(c *comid.Comid) error { if c.Triples.ReferenceValues == nil { - return fmt.Errorf("no reference values triples") + return errors.New("no reference values triples") } for i, rv := range c.Triples.ReferenceValues.Values { @@ -82,7 +99,7 @@ func extractPCERefVals(c *comid.Comid) error { func extractPCERefVal(rv comid.ValueTriple) error { class := rv.Environment.Class - if err := extractClassElements(class); err != nil { + if err := testextractClassElements(class); err != nil { return fmt.Errorf("extracting class: %w", err) } @@ -96,7 +113,7 @@ func extractPCERefVal(rv comid.ValueTriple) error { func extractPCEMeasurements(meas *comid.Measurements) error { if len(meas.Values) == 0 { - return fmt.Errorf("no measurements") + return errors.New("no measurements") } for i := range meas.Values { m := &meas.Values[0] @@ -105,7 +122,7 @@ func extractPCEMeasurements(meas *comid.Measurements) error { } if m.AuthorizedBy != nil { - err := decodeAuthorisedBy(m) + err := TestdecodeAuthorisedBy(m) if err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } @@ -117,11 +134,11 @@ func extractPCEMeasurements(meas *comid.Measurements) error { func decodePCEMValExtensions(m *comid.Measurement) error { val, err := m.Val.Extensions.Get("instanceid") if err != nil { - return fmt.Errorf("failed to decode instanceid from measurement extensions") + return errors.New("failed to decode instanceid from measurement extensions") } i, ok := val.(*TeeInstanceID) if !ok { - fmt.Printf("val was not pointer to teeInstanceID") + return errors.New("val was not pointer to teeInstanceID") } if i.IsBytes() { @@ -137,24 +154,25 @@ func decodePCEMValExtensions(m *comid.Measurement) error { } fmt.Printf("\nInstanceID: %d", val) } else { - return fmt.Errorf("teeinstanceid is neither integer or byte string") + return errors.New("teeinstanceid is neither integer or byte string") } val, err = m.Val.Extensions.Get("tcbcompsvn") if err != nil { - return fmt.Errorf("failed to decode teetcbcompsvn from measurement extensions") + return errors.New("failed to decode teetcbcompsvn from measurement extensions") } - tD, ok := val.(*TeeTcbCompSvn) + tcs, ok := val.(*TeeTcbCompSvn) if !ok { - fmt.Printf("val was not pointer to teetcbcompsvn") + return errors.New("val was not pointer to teetcbcompsvn") } - if err = tD.Valid(); err != nil { + if err = tcs.Valid(); err != nil { return fmt.Errorf("invalid computed SVN: %w", err) } + val, err = m.Val.Extensions.Get("pceid") if err != nil { - return fmt.Errorf("failed to decode tcbevalnum from measurement extensions") + return errors.New("failed to decode tcbevalnum from measurement extensions") } t, ok := val.(*TeePCEID) if !ok { @@ -166,24 +184,27 @@ func decodePCEMValExtensions(m *comid.Measurement) error { pceID := *t fmt.Printf("\npceID: %s", pceID) - err = extractSVN(tD) + err = extractCompSvn(tcs) if err != nil { - return fmt.Errorf("unable to extract TEE Digest: %w", err) + return fmt.Errorf("unable to extract TeeTcbCompSVN: %w", err) } return nil } -func extractSVN(s *TeeTcbCompSvn) error { +func extractCompSvn(s *TeeTcbCompSvn) error { if s == nil { - return fmt.Errorf("no TEE TCB Comp SVN") + return errors.New("no TEE TCB Comp SVN") } if len(*s) > 16 { - return fmt.Errorf("computed SVN cannot be greater than 16") + return errors.New("computed SVN cannot be greater than 16") } - for i, svn := range *s { - fmt.Printf("\nSVN[%d]: %d", i, svn) + for i, teesvn := range *s { + svn := teesvn // Avoid gosec: Implicit memory aliasing in for loop + if err := testextractTeeSvn(&svn); err != nil { + return fmt.Errorf("unable to extract SVN at index %d: %w", i, err) + } } return nil @@ -221,28 +242,44 @@ func Example_decode_PCE_CBOR() { panic(err) } - // Output: + // output: // OID: 2.16.840.1.113741.1.2.3.4.5 // Vendor: Intel Corporation // Model: TDX PCE TCB // InstanceID: 00112233445566778899aabbccddeeff // pceID: 0000 - // SVN[0]: 10 - // SVN[1]: 10 - // SVN[2]: 2 - // SVN[3]: 2 - // SVN[4]: 2 - // SVN[5]: 1 - // SVN[6]: 4 - // SVN[7]: 0 - // SVN[8]: 0 - // SVN[9]: 0 - // SVN[10]: 0 - // SVN[11]: 0 - // SVN[12]: 0 - // SVN[13]: 0 - // SVN[14]: 0 - // SVN[15]: 0 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 + // SVN Operator: greater_or_equal + // SVN Value: 2 + // SVN Operator: greater_or_equal + // SVN Value: 2 + // SVN Operator: greater_or_equal + // SVN Value: 2 + // SVN Operator: greater_or_equal + // SVN Value: 1 + // SVN Operator: greater_or_equal + // SVN Value: 4 + // SVN Operator: greater_or_equal + // SVN Value: 0 + // SVN Operator: greater_or_equal + // SVN Value: 0 + // SVN Operator: greater_or_equal + // SVN Value: 0 + // SVN Operator: greater_or_equal + // SVN Value: 0 + // SVN Operator: greater_or_equal + // SVN Value: 0 + // SVN Operator: greater_or_equal + // SVN Value: 0 + // SVN Operator: greater_or_equal + // SVN Value: 0 + // SVN Operator: greater_or_equal + // SVN Value: 0 + // SVN Operator: greater_or_equal + // SVN Value: 0 // CryptoKey Type: pkix-base64-key // CryptoKey Value: -----BEGIN PUBLIC KEY----- // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== @@ -298,42 +335,41 @@ func Example_encode_tdx_pce_refval_with_profile() { json, err := m.ToJSON() if err == nil { fmt.Printf("%s\n", string(json)) - } else { - fmt.Printf("\n To JSON Failed \n") } // Output: - // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e026b544458205043452054434281a101a3384c182d384f685043454944303031387c900102030405060708090a0b0c0d0e0f10 - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDX PCE TCB"}},"measurements":[{"value":{"instanceid":{"type":"uint","value":45},"pceid":"PCEID001","tcbcompsvn":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}}]}]}} + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e026b544458205043452054434281a101a3384c182d384f685043454944303031387c90d9ea6a820201d9ea6a820202d9ea6a820203d9ea6a820204d9ea6a820205d9ea6a820206d9ea6a820207d9ea6a820208d9ea6a820209d9ea6a82020ad9ea6a82020bd9ea6a82020cd9ea6a82020dd9ea6a82020ed9ea6a82020fd9ea6a820210 + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDX PCE TCB"}},"measurements":[{"value":{"instanceid":{"type":"uint","value":45},"pceid":"PCEID001","tcbcompsvn":[{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":1}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":2}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":3}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":4}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":5}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":6}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":7}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":8}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":9}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":12}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":13}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":14}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":15}}},{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":16}}}]}}]}]}} } func setTDXPCEMvalExtension(val *comid.Mval) error { instanceID, err := NewTeeInstanceID(TestUIntInstance) if err != nil { - return fmt.Errorf("unable to get teeinstanceID %w", err) + return fmt.Errorf("unable to get teeinstanceID: %w", err) } err = val.Extensions.Set("instanceid", instanceID) if err != nil { - return fmt.Errorf("unable to set teeinstanceID %w", err) + return fmt.Errorf("unable to set teeinstanceID: %w", err) } p, err := NewTeePCEID(TestPCEID) if err != nil { - return fmt.Errorf("unable to get NewTeepceID %w", err) + return fmt.Errorf("unable to get NewTeepceID: %w", err) } err = val.Extensions.Set("pceid", p) if err != nil { - return fmt.Errorf("unable to set teepceID %w", err) + return fmt.Errorf("unable to set teepceID: %w", err) } - c, err := NewTeeTcbCompSVN(TestCompSVN) + c, err := NewTeeTcbCompSvnExpression(TestCompSvn) if err != nil { - return fmt.Errorf("failed to get TeeTcbCompSVN %w", err) + return fmt.Errorf("failed to get TeeTcbCompSvn: %w", err) } err = val.Extensions.Set("tcbcompsvn", c) if err != nil { return fmt.Errorf("unable to set teetcbcompsvn: %w", err) } + return nil } diff --git a/comid/tdx-profile/example_qe_refval_test.go b/comid/tdx-profile/example_qe_refval_test.go index f314fb11..fd0c6c90 100644 --- a/comid/tdx-profile/example_qe_refval_test.go +++ b/comid/tdx-profile/example_qe_refval_test.go @@ -5,6 +5,7 @@ package tdx import ( _ "embed" + "errors" "fmt" "github.com/veraison/corim/comid" @@ -44,8 +45,10 @@ func Example_decode_QE_JSON() { // Vendor: Intel Corporation // Model: TDX QE TCB // miscselect: c0000000fbff0000 - // tcbEvalNum: 11 + // tcbEvalNum Operator: greater_or_equal + // tcbEvalNum Value: 11 // IsvProdID: 0303 + // mrsigner Digest Operator: member // mrsigner Digest Alg: 1 // mrsigner Digest Value: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 // mrsigner Digest Alg: 8 @@ -58,7 +61,7 @@ func Example_decode_QE_JSON() { func extractQERefVals(c *comid.Comid) error { if c.Triples.ReferenceValues == nil { - return fmt.Errorf("no reference values triples") + return errors.New("no reference values triples") } for i, rv := range c.Triples.ReferenceValues.Values { @@ -73,7 +76,7 @@ func extractQERefVals(c *comid.Comid) error { func extractQERefVal(rv comid.ValueTriple) error { class := rv.Environment.Class - if err := extractClassElements(class); err != nil { + if err := testextractClassElements(class); err != nil { return fmt.Errorf("extracting class: %w", err) } @@ -87,7 +90,7 @@ func extractQERefVal(rv comid.ValueTriple) error { func extractQEMeasurements(meas *comid.Measurements) error { if len(meas.Values) == 0 { - return fmt.Errorf("no measurements") + return errors.New("no measurements") } for i := range meas.Values { m := &meas.Values[0] @@ -96,7 +99,7 @@ func extractQEMeasurements(meas *comid.Measurements) error { } if m.AuthorizedBy != nil { - err := decodeAuthorisedBy(m) + err := TestdecodeAuthorisedBy(m) if err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } @@ -108,65 +111,55 @@ func extractQEMeasurements(meas *comid.Measurements) error { func decodeQEMValExtensions(m *comid.Measurement) error { val, err := m.Val.Extensions.Get("miscselect") if err != nil { - return fmt.Errorf("failed to decode miscselect from measurement extensions") + return errors.New("failed to decode miscselect from measurement extensions") } f, ok := val.(*TeeMiscSelect) if !ok { - fmt.Printf("val was not pointer to TeeMiscSelect") + return errors.New("val was not pointer to TeeMiscSelect") } miscselect := *f fmt.Printf("\nmiscselect: %x", miscselect) val, err = m.Val.Extensions.Get("tcbevalnum") if err != nil { - return fmt.Errorf("failed to decode tcbevalnum from measurement extensions") + return errors.New("failed to decode tcbevalnum from measurement extensions") } - t, ok := val.(*TeeTcbEvalNum) + t, ok := val.(*TeeTcbEvalNumber) if !ok { - fmt.Printf("val was not pointer to TeeTcbEvalNum") + return errors.New("val was not pointer to TeeTcbEvalNum") } tcbValNum := *t - fmt.Printf("\ntcbEvalNum: %d", tcbValNum) + if err = testextractTeeTcbEvalNum(&tcbValNum); err != nil { + return fmt.Errorf("failed to extract tcbevalnum: %w", err) + } val, err = m.Val.Extensions.Get("isvprodid") if err != nil { - return fmt.Errorf("failed to decode isvprodid from measurement extensions") + return errors.New("failed to decode isvprodid from measurement extensions") } tS, ok := val.(*TeeISVProdID) if !ok { - fmt.Printf("val was not pointer to IsvProdID") + return errors.New("val was not pointer to IsvProdID") } - if tS.IsBytes() { - val, err = tS.GetBytes() - if err != nil { - return fmt.Errorf("failed to decode isvprodid: %w", err) - } - fmt.Printf("\nIsvProdID: %x", val) - } else if tS.IsUint() { - val, err = tS.GetUint() - if err != nil { - return fmt.Errorf("failed to decode isvprodid: %w", err) - } - fmt.Printf("\nIsvProdID: %d", val) - } else { - return fmt.Errorf("isvprodid is neither integer or byte string") + if err = testextractTeeISVProdID(tS); err != nil { + return fmt.Errorf("failed to decode teeISVProdID from measurement extensions: %w", err) } val, err = m.Val.Extensions.Get("mrsigner") if err != nil { - return fmt.Errorf("failed to decode mrsigner from measurement extensions") + return errors.New("failed to decode mrsigner from measurement extensions") } tD, ok := val.(*TeeDigest) if !ok { - fmt.Printf("val was not pointer to TeeDigest") + return errors.New("val was not pointer to TeeDigest") } - err = extractDigest("mrsigner", tD) - if err != nil { - return fmt.Errorf("unable to extract TEE Digest: %w", err) + if err = testextractTeeDigest("mrsigner", tD); err != nil { + return fmt.Errorf("failed to extract tee mrsigner digest: %w", err) } + return nil } @@ -209,50 +202,61 @@ func Example_encode_tdx_QE_refval_without_profile() { json, err := m.ToJSON() if err == nil { fmt.Printf("%s\n", string(json)) - } else { - fmt.Printf("To JSON failed \n") } - // Output: - // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02703031323334353637383941424344454681a101a538480a385046c000fbff000038538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638540138550b - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":10,"miscselect":"wAD7/wAA","mrsigner":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"],"isvprodid":{"type":"uint","value":1},"tcbevalnum":11}}]}]}} + // output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02703031323334353637383941424344454681a101a53848d9ea6a82020a385046c000fbff00003853d9ea7482068282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f363854013855d9ea6a82020b + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"miscselect":"wAD7/wAA","mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]}},"isvprodid":{"type":"uint","value":1},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} } func setTDXQEMvalExtensions(val *comid.Mval) error { - svn := TeeSVN(10) - teeTcbEvNum := TeeTcbEvalNum(11) + svn, err := NewSvnExpression(TestISVSVN) + if err != nil { + return fmt.Errorf("unable to get isvsvn numeric: %w", err) + } + + teeTcbEvNum, err := NewTeeTcbEvalNumberNumeric(TestTCBEvalNum) + if err != nil { + return fmt.Errorf("unable to get tcbevalnum numeric: %w", err) + } + teeMiscSel := TeeMiscSelect([]byte{0xC0, 0x00, 0xFB, 0xFF, 0x00, 0x00}) // Taken from irim-qe-ref.diag // Taken below from irim-qe-ref.diag r := 1 isvProdID, err := NewTeeISVProdID(r) if err != nil { - return fmt.Errorf("unable to get isvprodid %w", err) + return fmt.Errorf("unable to get isvprodid: %w", err) } err = val.Extensions.Set("isvprodid", isvProdID) if err != nil { - return fmt.Errorf("unable to set isvprodid %w", err) + return fmt.Errorf("unable to set isvprodid: %w", err) } - err = val.Extensions.Set("isvsvn", &svn) + err = val.Extensions.Set("isvsvn", svn) if err != nil { - return fmt.Errorf("unable to set isvsvn %w", err) + return fmt.Errorf("unable to set isvsvn: %w", err) } - err = val.Extensions.Set("tcbevalnum", &teeTcbEvNum) + err = val.Extensions.Set("tcbevalnum", teeTcbEvNum) if err != nil { - return fmt.Errorf("unable to set tcbevalnum %w", err) + return fmt.Errorf("unable to set tcbevalnum: %w", err) } err = val.Extensions.Set("miscselect", &teeMiscSel) if err != nil { - return fmt.Errorf("unable to set miscselect %w", err) + return fmt.Errorf("unable to set miscselect: %w", err) } d := comid.NewDigests() d.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) d.AddDigest(swid.Sha384, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) - err = val.Extensions.Set("mrsigner", d) + td, err := NewTeeDigestExpr(MEM, *d) + if err != nil { + return fmt.Errorf("unable to get TeeDigest: %w", err) + } + + err = val.Extensions.Set("mrsigner", td) if err != nil { - return fmt.Errorf("unable to set mrsigner %w", err) + return fmt.Errorf("unable to set mrsigner: %w", err) } return nil } @@ -294,8 +298,10 @@ func Example_decode_QE_CBOR() { // Vendor: Intel Corporation // Model: SGX QE TCB // miscselect: a0b0c0d000000000 - // tcbEvalNum: 11 + // tcbEvalNum Operator: greater_or_equal + // tcbEvalNum Value: 11 // IsvProdID: 1 + // mrsigner Digest Operator: member // mrsigner Digest Alg: 1 // mrsigner Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a // mrsigner Digest Alg: 8 diff --git a/comid/tdx-profile/example_seam_refval_test.go b/comid/tdx-profile/example_seam_refval_test.go index 22388c64..c8aa5319 100644 --- a/comid/tdx-profile/example_seam_refval_test.go +++ b/comid/tdx-profile/example_seam_refval_test.go @@ -5,6 +5,7 @@ package tdx import ( _ "embed" + "errors" "fmt" "log" "time" @@ -45,12 +46,16 @@ func Example_decode_JSON() { // OID: 2.16.840.1.113741.1.2.3.4.5 // Vendor: Intel Corporation // Model: TDX SEAM - // tcbEvalNum: 11 + // tcbEvalNum Operator: greater_or_equal + // tcbEvalNum Value: 11 // IsvProdID: 0303 - // ISVSVN: 10 + // SVN Operator: greater_or_equal + // SVN Value: 10 // Attributes: f00a0b + // mrtee Digest Operator: member // mrtee Digest Alg: 1 // mrtee Digest Value: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 + // mrsigner Digest Operator: member // mrsigner Digest Alg: 1 // mrsigner Digest Value: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 // mrsigner Digest Alg: 8 @@ -101,12 +106,11 @@ func Example_encode_tdx_seam_refval_without_profile() { if err == nil { fmt.Printf("%s\n", string(json)) } else { - fmt.Printf("To JSON failed \n") + fmt.Printf("unable to format json %s, %s", err.Error(), json) } - - // Output: - // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc8038480a385142010138528182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7538538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36385442010138550b - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":10,"attributes":"AQE=","mrtee":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="],"mrsigner":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"],"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":11}}]}]}} + // output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc803848d9ea6a82020a38514201013852d9ea7482068182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d753853d9ea7482068282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638544201013855d9ea6a82020b + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} } func Example_encode_tdx_seam_refval_with_profile() { @@ -162,9 +166,9 @@ func Example_encode_tdx_seam_refval_with_profile() { fmt.Printf("\n To JSON Failed \n") } - // Output: - // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc8038480a385142010138528182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7538538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36385442010138550b - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":10,"attributes":"AQE=","mrtee":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="],"mrsigner":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"],"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":11}}]}]}} + // output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc803848d9ea6a82020a38514201013852d9ea7482068182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d753853d9ea7482068282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638544201013855d9ea6a82020b + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} } func Example_encode_tdx_seam_refval_direct() { @@ -211,58 +215,74 @@ func Example_encode_tdx_seam_refval_direct() { fmt.Printf("\n To JSON Failed \n") } - // Output: - // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc8038480a385142010138528182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7538538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36385442010138550b - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":10,"attributes":"AQE=","mrtee":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="],"mrsigner":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"],"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":11}}]}]}} + // output: + // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc803848d9ea6a82020a38514201013852d9ea7482068182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d753853d9ea7482068282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638544201013855d9ea6a82020b + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} } func setTDXSeamMvalExtensions(val *comid.Mval) error { tcbDate, _ := time.Parse(time.RFC3339, "2025-01-27T00:00:00Z") err := val.Extensions.Set("tcbdate", &tcbDate) if err != nil { - return fmt.Errorf("unable to set tcbDate %w", err) + return fmt.Errorf("unable to set tcbDate: %w", err) } r := []byte{0x01, 0x01} isvProdID, err := NewTeeISVProdID(r) if err != nil { - return fmt.Errorf("unable to get isvprodid %w", err) + return fmt.Errorf("unable to get isvprodid: %w", err) } err = val.Extensions.Set("isvprodid", isvProdID) if err != nil { - return fmt.Errorf("unable to set isvprodid %w", err) + return fmt.Errorf("unable to set isvprodid: %w", err) + } + svn, err := NewSvnExpression(TestISVSVN) + if err != nil { + return fmt.Errorf("unable to get isvsvn numeric: %w", err) + } + err = val.Extensions.Set("isvsvn", svn) + if err != nil { + return fmt.Errorf("unable to set isvsvn: %w", err) } - svn := TeeSVN(TestISVSVN) - err = val.Extensions.Set("isvsvn", &svn) + teeTcbEvNum, err := NewTeeTcbEvalNumberNumeric(TestTCBEvalNum) if err != nil { - return fmt.Errorf("unable to set isvsvn %w", err) + return fmt.Errorf("unable to get tcbevalnum numeric: %w", err) } - teeTcbEvNum := TeeTcbEvalNum(TestTCBEvalNum) - err = val.Extensions.Set("tcbevalnum", &teeTcbEvNum) + err = val.Extensions.Set("tcbevalnum", teeTcbEvNum) if err != nil { - return fmt.Errorf("unable to set tcbevalnum %w", err) + return fmt.Errorf("unable to set tcbevalnum: %w", err) } teeAttr, err := NewTeeAttributes(TestTeeAttributes) if err != nil { - return fmt.Errorf("unable to get teeAttributes %w", err) + return fmt.Errorf("unable to get teeAttributes: %w", err) } err = val.Extensions.Set("attributes", teeAttr) if err != nil { - return fmt.Errorf("unable to set attributes %w", err) + return fmt.Errorf("unable to set attributes: %w", err) } d := comid.NewDigests() d.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) - err = val.Extensions.Set("mrtee", d) + td, err := NewTeeDigestExpr(MEM, *d) + if err != nil { + return fmt.Errorf("unable to get TeeDigest: %w", err) + } + + err = val.Extensions.Set("mrtee", td) if err != nil { - return fmt.Errorf("unable to set mrtee %w", err) + return fmt.Errorf("unable to set mrtee: %w", err) } d = comid.NewDigests() d.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) d.AddDigest(swid.Sha384, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) - err = val.Extensions.Set("mrsigner", d) + td, err = NewTeeDigestExpr(MEM, *d) + if err != nil { + return fmt.Errorf("unable to get TeeDigest: %w", err) + } + + err = val.Extensions.Set("mrsigner", td) if err != nil { return fmt.Errorf("unable to set mrsigner %w", err) } @@ -272,65 +292,60 @@ func setTDXSeamMvalExtensions(val *comid.Mval) error { func decodeMValExtensions(m *comid.Measurement) error { val, err := m.Val.Extensions.Get("tcbevalnum") if err != nil { - return fmt.Errorf("failed to decode tcbevalnum from measurement extensions") + return fmt.Errorf("failed to decode tcbevalnum from measurement extensions: %w", err) } - f, ok := val.(*TeeTcbEvalNum) + f, ok := val.(*TeeTcbEvalNumber) if !ok { fmt.Printf("val was not pointer to TeeTcbEvalNum") } tcbValNum := *f - fmt.Printf("\ntcbEvalNum: %d", tcbValNum) + if err = testextractTeeTcbEvalNum(&tcbValNum); err != nil { + return fmt.Errorf("failed to extract tcbevalnum: %w", err) + } val, err = m.Val.Extensions.Get("isvprodid") if err != nil { - return fmt.Errorf("failed to decode isvprodid from measurement extensions") + return errors.New("failed to decode isvprodid from measurement extensions") } tS, ok := val.(*TeeISVProdID) if !ok { fmt.Printf("val was not pointer to IsvProdID") } - if tS.IsBytes() { - val, err = tS.GetBytes() - if err != nil { - return fmt.Errorf("failed to decode isvprodid: %w", err) - } - fmt.Printf("\nIsvProdID: %x", val) - } else if tS.IsUint() { - val, err = tS.GetUint() - if err != nil { - return fmt.Errorf("failed to decode isvprodid: %w", err) - } - fmt.Printf("\nIsvProdID: %d", val) - } else { - return fmt.Errorf("isvprodid is neither integer or byte string") + if err = testextractTeeISVProdID(tS); err != nil { + return fmt.Errorf("failed to decode teeISVProdID from measurement extensions: %w", err) } val, err = m.Val.Extensions.Get("isvsvn") if err != nil { - return fmt.Errorf("failed to decode isvsvn from measurement extensions") + return errors.New("failed to decode isvsvn from measurement extensions") } - tSV, ok := val.(*TeeSVN) + teesvn, ok := val.(*TeeSVN) if !ok { - fmt.Printf("val was not pointer to tee svn") + return errors.New("val was not pointer to tee svn") + } + err = teesvn.Valid() + if err != nil { + return fmt.Errorf("invalid tee svn: %w", err) } - fmt.Printf("\nISVSVN: %d", *tSV) - + err = testextractTeeSvn(teesvn) + if err != nil { + return fmt.Errorf("unable to extract tee svn: %w", err) + } val, err = m.Val.Extensions.Get("attributes") if err != nil { - return fmt.Errorf("failed to decode attributes from measurement extensions") + return errors.New("failed to decode attributes from measurement extensions") } tA, ok := val.(*TeeAttributes) if !ok { fmt.Printf("val was not pointer to teeAttributes") } - fmt.Printf("\nAttributes: %x", *tA) val, err = m.Val.Extensions.Get("mrtee") if err != nil { - return fmt.Errorf("failed to decode mrtee from measurement extensions") + return errors.New("failed to decode mrtee from measurement extensions") } tD, ok := val.(*TeeDigest) @@ -338,37 +353,26 @@ func decodeMValExtensions(m *comid.Measurement) error { fmt.Printf("val was not pointer to TeeDigest") } - err = extractDigest("mrtee", tD) - if err != nil { - return fmt.Errorf("unable to extract TEE Digest: %w", err) + if err = testextractTeeDigest("mrtee", tD); err != nil { + return fmt.Errorf("failed to decode mrtee from digest: %w", err) } val, err = m.Val.Extensions.Get("mrsigner") if err != nil { - return fmt.Errorf("failed to decode mrsigner from measurement extensions") + return fmt.Errorf("failed to decode mrsigner from measurement extensions: %w", err) } tD, ok = val.(*TeeDigest) if !ok { - fmt.Printf("val was not pointer to TeeDigest") + return errors.New("val was not pointer to TeeDigest") } - err = extractDigest("mrsigner", tD) - if err != nil { - return fmt.Errorf("unable to extract TEE Digest: %w", err) + if err := testextractTeeDigest("mrsigner", tD); err != nil { + return fmt.Errorf("failed to extarct mrsigner digest: %w", err) } return nil } -func decodeAuthorisedBy(m *comid.Measurement) error { - if err := m.AuthorizedBy.Valid(); err != nil { - return fmt.Errorf("invalid cryptokey: %w", err) - } - fmt.Printf("\nCryptoKey Type: %s", m.AuthorizedBy.Type()) - fmt.Printf("\nCryptoKey Value: %s", m.AuthorizedBy.String()) - return nil -} - var ( // test cases are based on diag files here: // https://github.com/ietf-rats-wg/draft-ietf-rats-corim/tree/main/cddl/examples @@ -405,12 +409,16 @@ func Example_decode_CBOR() { // OID: 2.16.840.1.113741.1.2.3.4.3 // Vendor: Intel Corporation // Model: TDX SEAM - // tcbEvalNum: 11 + // tcbEvalNum Operator: greater_or_equal + // tcbEvalNum Value: 11 // IsvProdID: abcd - // ISVSVN: 6 + // SVN Operator: greater_or_equal + // SVN Value: 6 // Attributes: 0102 + // mrtee Digest Operator: member // mrtee Digest Alg: 1 // mrtee Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a + // mrsigner Digest Operator: member // mrsigner Digest Alg: 1 // mrsigner Digest Value: a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a // mrsigner Digest Alg: 8 @@ -419,11 +427,12 @@ func Example_decode_CBOR() { // CryptoKey Value: -----BEGIN PUBLIC KEY----- // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg== // -----END PUBLIC KEY----- + } func extractRefVals(c *comid.Comid) error { if c.Triples.ReferenceValues == nil { - return fmt.Errorf("no reference values triples") + return errors.New("no reference values triples") } for i, rv := range c.Triples.ReferenceValues.Values { @@ -437,7 +446,7 @@ func extractRefVals(c *comid.Comid) error { func extractSeamRefVal(rv comid.ValueTriple) error { class := rv.Environment.Class - if err := extractClassElements(class); err != nil { + if err := testextractClassElements(class); err != nil { return fmt.Errorf("extracting class: %w", err) } @@ -451,7 +460,7 @@ func extractSeamRefVal(rv comid.ValueTriple) error { func extractSeamMeasurements(meas *comid.Measurements) error { if len(meas.Values) == 0 { - return fmt.Errorf("no measurements") + return errors.New("no measurements") } for i := range meas.Values { m := &meas.Values[0] @@ -460,7 +469,7 @@ func extractSeamMeasurements(meas *comid.Measurements) error { } if m.AuthorizedBy != nil { - err := decodeAuthorisedBy(m) + err := TestdecodeAuthorisedBy(m) if err != nil { return fmt.Errorf("extracting measurement at index %d: %w", i, err) } @@ -468,49 +477,3 @@ func extractSeamMeasurements(meas *comid.Measurements) error { } return nil } - -func extractClassElements(c *comid.Class) error { - if c == nil { - return fmt.Errorf("no class") - } - - classID := c.ClassID - - if classID == nil { - return fmt.Errorf("no class-id") - } - - if classID.Type() != comid.OIDType { - return fmt.Errorf("class id is not an oid") - } - - fmt.Printf("OID: %s", classID.Value.String()) - - if c.Vendor == nil { - return fmt.Errorf("no Vendor") - } - fmt.Printf("\nVendor: %s", *c.Vendor) - - if c.Model == nil { - return fmt.Errorf("no Model") - } - fmt.Printf("\nModel: %s", *c.Model) - - return nil -} - -func extractDigest(typ string, d *TeeDigest) error { - if d == nil { - return fmt.Errorf("no TEE digest") - } - - if typ != "mrsigner" && typ != "mrtee" { - return fmt.Errorf("invalid type for TEE digest: %s", typ) - } - for _, digest := range *d { - fmt.Printf("\n%s Digest Alg: %d", typ, digest.HashAlgID) - fmt.Printf("\n%s Digest Value: %x", typ, digest.HashValue) - } - - return nil -} diff --git a/comid/tdx-profile/mval_extensions.go b/comid/tdx-profile/mval_extensions.go index af4e0f2e..20f3fd52 100644 --- a/comid/tdx-profile/mval_extensions.go +++ b/comid/tdx-profile/mval_extensions.go @@ -15,19 +15,19 @@ import ( // MValExtensions contains the Intel TDX profile extensions which can appear in // both Reference Values and Endorsed Values type MValExtensions struct { - TeeTcbDate *time.Time `cbor:"-72,keyasint,omitempty" json:"tcbdate,omitempty"` - TeeISVSVN *TeeSVN `cbor:"-73,keyasint,omitempty" json:"isvsvn,omitempty"` - TeeInstanceID *TeeInstanceID `cbor:"-77,keyasint,omitempty" json:"instanceid,omitempty"` - TeePCEID *TeePCEID `cbor:"-80,keyasint,omitempty" json:"pceid,omitempty"` - TeeMiscSelect *TeeMiscSelect `cbor:"-81,keyasint,omitempty" json:"miscselect,omitempty"` - TeeAttributes *TeeAttributes `cbor:"-82,keyasint,omitempty" json:"attributes,omitempty"` - TeeMrTee *TeeDigest `cbor:"-83,keyasint,omitempty" json:"mrtee,omitempty"` - TeeMrSigner *TeeDigest `cbor:"-84,keyasint,omitempty" json:"mrsigner,omitempty"` - TeeISVProdID *TeeISVProdID `cbor:"-85,keyasint,omitempty" json:"isvprodid,omitempty"` - TeeTcbEvalNum *TeeTcbEvalNum `cbor:"-86,keyasint,omitempty" json:"tcbevalnum,omitempty"` - TeeTcbStatus *TeeTcbStatus `cbor:"-88,keyasint,omitempty" json:"tcbstatus,omitempty"` - TeeAdvisoryIDs *TeeAdvisoryIDs `cbor:"-89,keyasint,omitempty" json:"advisoryids,omitempty"` - TeeEpoch *time.Time `cbor:"-90, keyasint,omitempty" json:"epoch,omitempty"` + TeeTcbDate *time.Time `cbor:"-72,keyasint,omitempty" json:"tcbdate,omitempty"` + TeeISVSVN *TeeSVN `cbor:"-73,keyasint,omitempty" json:"isvsvn,omitempty"` + TeeInstanceID *TeeInstanceID `cbor:"-77,keyasint,omitempty" json:"instanceid,omitempty"` + TeePCEID *TeePCEID `cbor:"-80,keyasint,omitempty" json:"pceid,omitempty"` + TeeMiscSelect *TeeMiscSelect `cbor:"-81,keyasint,omitempty" json:"miscselect,omitempty"` + TeeAttributes *TeeAttributes `cbor:"-82,keyasint,omitempty" json:"attributes,omitempty"` + TeeMrTee *TeeDigest `cbor:"-83,keyasint,omitempty" json:"mrtee,omitempty"` + TeeMrSigner *TeeDigest `cbor:"-84,keyasint,omitempty" json:"mrsigner,omitempty"` + TeeISVProdID *TeeISVProdID `cbor:"-85,keyasint,omitempty" json:"isvprodid,omitempty"` + TeeTcbEvalNum *TeeTcbEvalNumber `cbor:"-86,keyasint,omitempty" json:"tcbevalnum,omitempty"` + TeeTcbStatus *TeeTcbStatus `cbor:"-88,keyasint,omitempty" json:"tcbstatus,omitempty"` + TeeAdvisoryIDs *TeeAdvisoryIDs `cbor:"-89,keyasint,omitempty" json:"advisoryids,omitempty"` + TeeEpoch *time.Time `cbor:"-90, keyasint,omitempty" json:"epoch,omitempty"` TeeCryptoKeys *comid.CryptoKeys `cbor:"-91, keyasint,omitempty" json:"teecryptokeys,omitempty"` TeeTCBCompSvn *TeeTcbCompSvn `cbor:"-125, keyasint,omitempty" json:"tcbcompsvn,omitempty"` diff --git a/comid/tdx-profile/numeric_expression.go b/comid/tdx-profile/numeric_expression.go new file mode 100644 index 00000000..22cc42af --- /dev/null +++ b/comid/tdx-profile/numeric_expression.go @@ -0,0 +1,41 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import "fmt" + +type NumericExpression struct { + _ struct{} `cbor:",toarray"` + NumericOperator Operator `json:"numeric-operator"` + NumericType NumericType `json:"numeric-type"` +} + +type TaggedNumericExpression NumericExpression + +func (o NumericExpression) Valid() error { + switch o.NumericOperator { + case GT, GE, LT, LE: + default: + return fmt.Errorf("invalid Operator %d", o.NumericOperator) + } + return nil +} + +// NewTaggedNumericExpression creates a new TaggedNumericExpression from the +// supplied operator and an interface value. Allowed values are int, uint and float +func NewTaggedNumericExpression(op uint, val any) (*TaggedNumericExpression, error) { + var tnum TaggedNumericExpression + switch op { + case GT, GE, LT, LE: + numType, err := NewNumericType(val) + if err != nil { + return nil, fmt.Errorf("invalid NumericType type: %w", err) + } + numeric := NumericExpression{NumericOperator: Operator(op), NumericType: *numType} + tnum = TaggedNumericExpression(numeric) + return &tnum, nil + default: + return nil, fmt.Errorf("invalid numeric operator %d", op) + } +} diff --git a/comid/tdx-profile/numeric_type.go b/comid/tdx-profile/numeric_type.go new file mode 100644 index 00000000..de02493e --- /dev/null +++ b/comid/tdx-profile/numeric_type.go @@ -0,0 +1,186 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "encoding/json" + "fmt" + + "github.com/fxamacker/cbor/v2" + "github.com/veraison/corim/encoding" +) + +// NumericType represents the abstraction of a numeric type, allowed values are int/uint/float +type NumericType struct { + val interface{} +} + +// NewNumericType creates a new NumericType from the +// supplied interface. The supported types are unsigned integers, signed integers and +// float64 +func NewNumericType(val any) (*NumericType, error) { + switch t := val.(type) { + case uint, uint64: + return &NumericType{val: t}, nil + case float64: + return &NumericType{val: t}, nil + case int: + return &NumericType{val: t}, nil + default: + return nil, fmt.Errorf("unsupported NumericType type: %T", t) + } +} + +func (o *NumericType) SetNumericType(val any) error { + switch t := val.(type) { + case uint, uint64: + o.val = val + case float64: + o.val = val + case int: + o.val = val + default: + return fmt.Errorf("unsupported NumericType type: %T", t) + } + return nil +} + +// IsFloat returns true if NumericType is of type float64 array +func (o NumericType) IsFloat() bool { + return isType[float64](o.val) +} + +// IsUnit returns true if NumericType is of type unsigned integer +func (o NumericType) IsUint() bool { + return isType[uint64](o.val) || isType[uint](o.val) +} + +// IsInt returns true if NumericType is of type integer +func (o NumericType) IsInt() bool { + return isType[int](o.val) +} + +// GetUint returns unsigned integer NumericType +func (o NumericType) GetUint() (uint, error) { + switch t := o.val.(type) { + case uint64: + return uint(t), nil + case uint: + return t, nil + default: + return 0, fmt.Errorf("NumericType type is: %T", t) + } +} + +// GetInt returns integer NumericType +func (o NumericType) GetInt() (int, error) { + switch t := o.val.(type) { + case int: + return t, nil + default: + return 0, fmt.Errorf("NumericType type is: %T", t) + } +} + +// GetFloat returns float NumericType +func (o NumericType) GetFloat() (float64, error) { + switch t := o.val.(type) { + case float64: + return t, nil + default: + return 0, fmt.Errorf("NumericType type is: %T", t) + } +} + +// MarshalCBOR Marshals NumericType to CBOR bytes +func (o NumericType) MarshalCBOR() ([]byte, error) { + return cbor.Marshal(o.val) +} + +// UnmarshalCBOR UnMarshals supplied CBOR bytes to NumericType +func (o *NumericType) UnmarshalCBOR(data []byte) error { + return cbor.Unmarshal(data, &o.val) +} + +// Valid checks for validity of NumericType and returns an error if Invalid +func (o NumericType) Valid() error { + if o.val == nil { + return fmt.Errorf("empty NumericType") + } + switch t := o.val.(type) { + case uint, uint64, float64, int: + return nil + default: + return fmt.Errorf("unsupported NumericType type: %T", t) + } +} + +// MarshalJSON Marshals NumericType to JSON +func (o NumericType) MarshalJSON() ([]byte, error) { + if o.Valid() != nil { + return nil, fmt.Errorf("invalid NumericType") + } + var ( + v encoding.TypeAndValue + b []byte + err error + ) + switch t := o.val.(type) { + case uint, uint64: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: UintType, Value: b} + case int: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: IntType, Value: b} + case float64: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: FloatType, Value: b} + default: + return nil, fmt.Errorf("unknown type %T for NumericType", t) + } + return json.Marshal(v) +} + +// UnmarshalJSON UnMarshals supplied JSON buffer to NumericType +func (o *NumericType) UnmarshalJSON(data []byte) error { + var v encoding.TypeAndValue + + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + switch v.Type { + case UintType: + var x uint + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal NumericType of type uint: %w", err) + } + o.val = x + case IntType: + var x int + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal NumericType of type int: %w", err) + } + o.val = x + case FloatType: + var x float64 + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal NumericType of type float: %w", err) + } + o.val = x + } + return nil +} diff --git a/comid/tdx-profile/numeric_type_test.go b/comid/tdx-profile/numeric_type_test.go new file mode 100644 index 00000000..57da7674 --- /dev/null +++ b/comid/tdx-profile/numeric_type_test.go @@ -0,0 +1,92 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const TestInt = 10 +const TestUint = uint(10) +const TestFloat = 10.123 + +func TestNumericType_NewNumericType(t *testing.T) { + _, err := NewNumericType(uint(10)) + require.NoError(t, err) + _, err = NewNumericType(10) + require.NoError(t, err) + _, err = NewNumericType(10.123) + require.NoError(t, err) +} + +func TestNumericType_SetNumericType(t *testing.T) { + o := &NumericType{} + err := o.SetNumericType(uint(10)) + require.NoError(t, err) + err = o.SetNumericType(10) + require.NoError(t, err) + err = o.SetNumericType(10.123) + require.NoError(t, err) +} + +func TestNumericType_IsType(t *testing.T) { + o := &NumericType{val: 10} + b := o.IsInt() + require.True(t, b) + o = &NumericType{val: uint(10)} + b = o.IsUint() + require.True(t, b) + o = &NumericType{val: 10.00} + require.True(t, b) +} + +func TestNumericType_GetType(t *testing.T) { + o := &NumericType{val: 10} + i, err := o.GetInt() + require.NoError(t, err) + assert.Equal(t, TestInt, i) + + o = &NumericType{val: TestUint} + k, err := o.GetUint() + require.NoError(t, err) + assert.Equal(t, TestUint, k) + + o = &NumericType{val: TestFloat} + f, err := o.GetFloat() + require.NoError(t, err) + assert.Equal(t, TestFloat, f) +} + +func TestNumericType_GetType_NOK(t *testing.T) { + o := &NumericType{val: 10} + i, err := o.GetInt() + require.NoError(t, err) + assert.Equal(t, TestInt, i) + + o = &NumericType{val: TestUint} + k, err := o.GetUint() + require.NoError(t, err) + assert.Equal(t, TestUint, k) + + o = &NumericType{val: TestFloat} + f, err := o.GetFloat() + require.NoError(t, err) + assert.Equal(t, TestFloat, f) +} + +func TestNumericType_Valid_OK(t *testing.T) { + o := NumericType{val: 10} + err := o.Valid() + require.NoError(t, err) +} + +func TestNumericType_Valid_NOK(t *testing.T) { + expectedErr := "unsupported NumericType type: string" + o := NumericType{val: "test"} + err := o.Valid() + assert.EqualError(t, err, expectedErr) +} diff --git a/comid/tdx-profile/operator.go b/comid/tdx-profile/operator.go new file mode 100644 index 00000000..0d0c0f89 --- /dev/null +++ b/comid/tdx-profile/operator.go @@ -0,0 +1,68 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "encoding/json" + "fmt" +) + +type Operator uint + +const ( + EQ = iota // EQual + GT // greater than + GE // greater than or equal to + LT // less than + LE // less than or equal to + NOP // undefined + MEM // member + NMEM // no member + SUB // sub-set + SUP // super set + DIS // dis-joint +) + +var ( + StringToNumericOperator = map[string]Operator{ + "equal": EQ, + "greater_than": GT, + "greater_or_equal": GE, + "less_than": LT, + "less_or equal": LE, + "nop": NOP, + "member": MEM, + "non_member": NMEM, + "subset": SUB, + "superset": SUP, + "disjoint": DIS, + } + NumericOperatorToString = map[Operator]string{ + EQ: "equal", + GT: "greater_than", + GE: "greater_or_equal", + LT: "less_than", + LE: "less_or equal", + NOP: "nop", + MEM: "member", + NMEM: "non_member", + SUB: "subset", + SUP: "superset", + DIS: "disjoint", + } +) + +func (o Operator) MarshalJSON() ([]byte, error) { + return json.Marshal(NumericOperatorToString[o]) +} + +func (o *Operator) UnmarshalJSON(data []byte) error { + var str string + err := json.Unmarshal(data, &str) + if err != nil { + return fmt.Errorf("unable to unmarshal operator: %w", err) + } + *o = StringToNumericOperator[str] + return nil +} diff --git a/comid/tdx-profile/set_expression.go b/comid/tdx-profile/set_expression.go new file mode 100644 index 00000000..d129dac7 --- /dev/null +++ b/comid/tdx-profile/set_expression.go @@ -0,0 +1,62 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "fmt" + + "github.com/veraison/corim/comid" +) + +type SetDigest comid.Digests + +type SetString []string + +type SetStringExpression struct { + _ struct{} `cbor:",toarray"` + SetOperator Operator `json:"set-operator"` + SetString SetString `json:"set-string"` +} + +type SetDigestExpression struct { + _ struct{} `cbor:",toarray"` + SetOperator Operator `json:"set-operator"` + SetDigest SetDigest `json:"set-digest"` +} + +type TaggedSetStringExpression SetStringExpression + +type TaggedSetDigestExpression SetDigestExpression + +// NewTaggedSetStringExpression creates a TaggedSetStringExpression from +// the supplied operator and an array of strings +func NewTaggedSetStringExpression(op uint, val []string) (*TaggedSetStringExpression, error) { + switch op { + case MEM, NMEM: + if len(val) == 0 { + return nil, fmt.Errorf("zero len string array supplied") + } + set := SetStringExpression{SetOperator: Operator(op), SetString: SetString(val)} + tset := TaggedSetStringExpression(set) + return &tset, nil + default: + return nil, fmt.Errorf("invalid set operator %d", op) + } +} + +// NewTaggedSetDigestExpression creates a TaggedSetStringExpression from +// the supplied operator and Digests +func NewTaggedSetDigestExpression(op uint, val comid.Digests) (*TaggedSetDigestExpression, error) { + switch op { + case MEM, NMEM: + if len(val) == 0 { + return nil, fmt.Errorf("no Digests supplied") + } + set := SetDigestExpression{SetOperator: Operator(op), SetDigest: SetDigest(val)} + tset := TaggedSetDigestExpression(set) + return &tset, nil + default: + return nil, fmt.Errorf("invalid set operator %d", op) + } +} diff --git a/comid/tdx-profile/tee_tcb_eval_num.go b/comid/tdx-profile/tee_tcb_eval_num.go new file mode 100644 index 00000000..fe6417ae --- /dev/null +++ b/comid/tdx-profile/tee_tcb_eval_num.go @@ -0,0 +1,133 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// nolint:dupl +package tdx + +import ( + "encoding/json" + "fmt" + + "github.com/veraison/corim/encoding" +) + +// TeeTcbEvalNumber holds a TeeTcbEvalNumber, supported formats are uint and TaggedNumericExpression +type TeeTcbEvalNumber struct { + val interface{} +} + +func NewTeeTcbEvalNumberNumeric(val uint) (*TeeTcbEvalNumber, error) { + tnum, err := NewTaggedNumericExpression(GE, val) + if err != nil { + return nil, err + } + return &TeeTcbEvalNumber{val: *tnum}, nil +} + +func NewTeeTcbEvalNumberUint(val uint) (*TeeTcbEvalNumber, error) { + return &TeeTcbEvalNumber{val: val}, nil +} + +func (o TeeTcbEvalNumber) Valid() error { + switch t := o.val.(type) { + case uint, uint64: + return nil + case TaggedNumericExpression: + if t.NumericOperator != GE { + return fmt.Errorf("unknown operator %d for TeeTcbEvalNumber", t.NumericOperator) + } + return nil + default: + return fmt.Errorf("unknown type %T for TeeTcbEvalNumber", t) + } +} + +func (o TeeTcbEvalNumber) IsExpression() bool { + return isType[TaggedNumericExpression](o.val) +} + +func (o TeeTcbEvalNumber) IsUint() bool { + return isType[uint](o.val) || isType[uint64](o.val) +} + +func (o TeeTcbEvalNumber) GetUint() (uint, error) { + switch t := o.val.(type) { + case uint: + return t, nil + case uint64: + return uint(t), nil + default: + return 0, fmt.Errorf("unknown type %T for TeeTcbEvalNumber", t) + } +} + +func (o TeeTcbEvalNumber) GetNumericExpression() (TaggedNumericExpression, error) { + switch t := o.val.(type) { + case TaggedNumericExpression: + return t, nil + default: + return TaggedNumericExpression{}, fmt.Errorf("unknown type %T for TeeTcbEvalNumber", t) + } +} + +func (o *TeeTcbEvalNumber) MarshalJSON() ([]byte, error) { + var ( + v encoding.TypeAndValue + b []byte + err error + ) + switch t := o.val.(type) { + case uint, uint64: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: UintType, Value: b} + case TaggedNumericExpression: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: NumericExprType, Value: b} + default: + return nil, fmt.Errorf("unknown type %T for TeeTcbEvalNumber", t) + } + return json.Marshal(v) +} + +// UnmarshalJSON UnMarshals supplied JSON bytes to TeeTcbEvalNumber +func (o *TeeTcbEvalNumber) UnmarshalJSON(data []byte) error { + var v encoding.TypeAndValue + + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + switch v.Type { + case UintType: + var x uint + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeTcbEvalNumber of type uint: %w", err) + } + o.val = x + case NumericExprType: + var x NumericExpression + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeTcbEvalNumber of type numeric: %w", err) + } + o.val = TaggedNumericExpression(x) + } + return nil +} + +// MarshalCBOR Marshals TeeTcbEvalNumber to CBOR +func (o TeeTcbEvalNumber) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.val) +} + +// UnmarshalCBOR UnMarshals supplied CBOR bytes to TeeTcbEvalNumber +func (o *TeeTcbEvalNumber) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.val) +} diff --git a/comid/tdx-profile/tee_tcb_eval_num_test.go b/comid/tdx-profile/tee_tcb_eval_num_test.go new file mode 100644 index 00000000..2b672ef2 --- /dev/null +++ b/comid/tdx-profile/tee_tcb_eval_num_test.go @@ -0,0 +1,87 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTeeTcbEvalNumber_NewTeeTcbEvalNumberNumeric_OK(t *testing.T) { + _, err := NewTeeTcbEvalNumberNumeric(TestTCBEvalNum) + require.NoError(t, err) +} + +func TestTeeTcbEvalNumber_NewTeeTcbEvalNumberUint_OK(t *testing.T) { + _, err := NewTeeTcbEvalNumberUint(TestTCBEvalNum) + require.NoError(t, err) +} + +func TestTeeTcbEvalNumber_Valid_OK(t *testing.T) { + tcb := TeeTcbEvalNumber{val: TestTCBEvalNum} + err := tcb.Valid() + require.NoError(t, err) +} + +func TestTeeTcbEvalNumber_Valid_NOK(t *testing.T) { + expectedErr := "unknown type int for TeeTcbEvalNumber" + en := TeeTcbEvalNumber{val: -10} + err := en.Valid() + assert.EqualError(t, err, expectedErr) +} + +func TestTeeTcbEvalNumber_GetUint_OK(t *testing.T) { + tcb := TeeTcbEvalNumber{val: TestTCBEvalNum} + val, err := tcb.GetUint() + require.NoError(t, err) + require.Equal(t, val, TestTCBEvalNum) +} + +func TestTeeTcbEvalNumber_IsUint_OK(t *testing.T) { + tcb := TeeTcbEvalNumber{val: TestTCBEvalNum} + b := tcb.IsUint() + require.True(t, b) +} + +func TestTeeTcbEvalNumber_JSON1(t *testing.T) { + expectedErr := "unknown type int for TeeTcbEvalNumber" + en := TeeTcbEvalNumber{val: -10} + err := en.Valid() + assert.EqualError(t, err, expectedErr) +} + +func TestTeeTcbEvalNumber_JSON(t *testing.T) { + + for _, tv := range []struct { + input interface{} + ExpectedBytes []byte + }{ + { + input: uint(10), + ExpectedBytes: []byte(`{"type":"uint","value":10}`), + }, + + { + input: TaggedNumericExpression{NumericOperator: GE, NumericType: NumericType{val: uint(100)}}, + ExpectedBytes: []byte(`{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":100}}}`), + }, + } { + + t.Run("test", func(t *testing.T) { + tcb := &TeeTcbEvalNumber{val: tv.input} + + data, err := tcb.MarshalJSON() + require.NoError(t, err) + fmt.Printf("received string %s", string(data)) + assert.Equal(t, tv.ExpectedBytes, data) + + out := &TeeTcbEvalNumber{} + err = out.UnmarshalJSON(data) + require.NoError(t, err) + }) + } +} diff --git a/comid/tdx-profile/teeadvisoryids.go b/comid/tdx-profile/teeadvisoryids.go index 7662e799..3cc2802b 100644 --- a/comid/tdx-profile/teeadvisoryids.go +++ b/comid/tdx-profile/teeadvisoryids.go @@ -4,58 +4,162 @@ // nolint:dupl package tdx -import "fmt" +import ( + "encoding/json" + "fmt" -type TeeAdvisoryIDs setType + "github.com/veraison/corim/encoding" +) -// NewTeeAvisoryIDs create a new TeeAvisoryIDs from the -// supplied interface array and returns a pointer to -// the AdvisoryIDs. Only -// Advisory IDs of string type are supported -func NewTeeAvisoryIDs(val []any) (*TeeAdvisoryIDs, error) { - var adv TeeAdvisoryIDs +// TeeAdvisoryIDs, holds the Advisory IDs. Allowed values are an array of strings OR +// An array of strings expressed in an expression +type TeeAdvisoryIDs struct { + val interface{} +} + +// NewTeeAvisoryIDsExpr create a new TeeAvisoryIDs from the +// supplied operator and an array of strings +func NewTeeAdvisoryIDsExpr(op uint, val []string) (*TeeAdvisoryIDs, error) { if len(val) == 0 { - return nil, fmt.Errorf("zero len TeeAdvisoryIDs") + return nil, fmt.Errorf("zero len value for TeeAdvisoryID") } - - for i, v := range val { - switch t := v.(type) { - case string: - adv = append(adv, t) - default: - return nil, fmt.Errorf("invalid type: %T for AdvisoryIDs at index: %d", t, i) + switch op { + case MEM, NMEM: + set, err := NewTaggedSetStringExpression(op, val) + if err != nil { + return nil, fmt.Errorf("unable to get NewTeeAdvisoryIDExpr %w", err) } + return &TeeAdvisoryIDs{val: *set}, nil + default: + return nil, fmt.Errorf("invalid operator : %d", op) } - return &adv, nil +} + +// NewTeeAdvisoryIDsString create a new TeeAvisoryIDs from the +// supplied array of strings +func NewTeeAdvisoryIDsString(val []string) (*TeeAdvisoryIDs, error) { + if len(val) == 0 { + return nil, fmt.Errorf("zero len value for TeeAdvisoryID") + } + return &TeeAdvisoryIDs{val: val}, nil } // AddTeeAdvisoryIDs add supplied AvisoryIDs to existing AdvisoryIDs -func (o *TeeAdvisoryIDs) AddTeeAdvisoryIDs(val []any) error { - for i, v := range val { - switch t := v.(type) { - case string: - *o = append(*o, t) - default: - return fmt.Errorf("invalid type: %T for AdvisoryIDs at index: %d", t, i) +func (o *TeeAdvisoryIDs) AddTeeAdvisoryIDs(op uint, val []string) (*TeeAdvisoryIDs, error) { + if len(val) == 0 { + return nil, fmt.Errorf("zero len value for TeeAdvisoryID") + } + switch t := o.val.(type) { + case []string: + t = append(t, val...) + o.val = t + case TaggedSetStringExpression: + if t.SetOperator != Operator(op) { + return nil, fmt.Errorf("operator mis-match Advisory Op: %d, Input Op: %d", t.SetOperator, op) } + t.SetString = append(t.SetString, val...) + o.val = t + default: + return nil, fmt.Errorf("unknown type %T for TeeAdvisoryIDs", t) } - return nil + return o, nil } // Valid checks for validity of TeeAdvisoryIDs and // returns an error, if invalid func (o TeeAdvisoryIDs) Valid() error { - if len(o) == 0 { - return fmt.Errorf("empty AdvisoryIDs") + if o.val == nil { + return fmt.Errorf("TeeAdvisoryID not set") + } + switch t := o.val.(type) { + case []string: + if len(t) == 0 { + return fmt.Errorf("TeeAdvisoryID not set") + } + case TaggedSetStringExpression: + if t.SetOperator != MEM && t.SetOperator != NMEM { + return fmt.Errorf("invalid operator in a TeeAdvisoryID: %d", t.SetOperator) + } + if len(t.SetString) == 0 { + return fmt.Errorf("TeeAdvisoryID not set") + } + default: + return fmt.Errorf("unknown type %T for TeeAdvisoryIDs", t) + + } + return nil +} + +// IsString returns true if TeeAdvisoryIDs is slice of strings +func (o TeeAdvisoryIDs) IsString() bool { + return isType[[]string](o.val) +} + +// IsStringExpr returns true if IsStringExpr is SetStringExpr +func (o TeeAdvisoryIDs) IsStringExpr() bool { + return isType[TaggedSetStringExpression](o.val) +} +func (o TeeAdvisoryIDs) MarshalJSON() ([]byte, error) { + var ( + v encoding.TypeAndValue + b []byte + err error + ) + switch t := o.val.(type) { + case []string: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: StringType, Value: b} + case TaggedSetStringExpression: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: StringExprType, Value: b} + default: + return nil, fmt.Errorf("unknown type %T for TeeAdvisoryIDs", t) } - for i, v := range o { - switch t := v.(type) { - case string: - continue - default: - return fmt.Errorf("invalid type: %T for AdvisoryIDs at index: %d", t, i) + return json.Marshal(v) +} + +// UnmarshalJSON UnMarshals supplied JSON bytes to TeeAdvisoryIDs +func (o *TeeAdvisoryIDs) UnmarshalJSON(data []byte) error { + var v encoding.TypeAndValue + + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + switch v.Type { + case StringType: + var x []string + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeAdvisoryIDs of type string: %w", err) + } + o.val = x + case StringExprType: + var x SetStringExpression + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeAdvisoryIDs of type set expression: %w", err) } + o.val = TaggedSetStringExpression(x) + default: + return fmt.Errorf("unknown type %T for TeeAdvisoryIDs", v.Type) } return nil } + +// MarshalCBOR Marshals TeeAdvisoryIDs to CBOR +func (o TeeAdvisoryIDs) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.val) +} + +// UnmarshalCBOR UnMarshals supplied CBOR bytes to TeeAdvisoryIDs +func (o *TeeAdvisoryIDs) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.val) +} diff --git a/comid/tdx-profile/teeadvisoryids_test.go b/comid/tdx-profile/teeadvisoryids_test.go index 01785989..e30f765a 100644 --- a/comid/tdx-profile/teeadvisoryids_test.go +++ b/comid/tdx-profile/teeadvisoryids_test.go @@ -4,75 +4,98 @@ package tdx import ( + "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func initAdvisoryIDs() []any { - s := make([]any, len(TestAdvisoryIDs)) - for i := range TestAdvisoryIDs { - s[i] = TestAdvisoryIDs[i] - } - return s +func TestAdvisoryIDs_NewTeeAvisoryIDsExpr_OK(t *testing.T) { + a := TestAdvisoryIDs + _, err := NewTeeAdvisoryIDsExpr(MEM, a) + require.Nil(t, err) } -func TestAdvisoryIDs_NewTeeAvisoryIDs_OK(t *testing.T) { - a := initAdvisoryIDs() - _, err := NewTeeAvisoryIDs(a) +func TestAdvisoryIDs_NewTeeAvisoryIDsString_OK(t *testing.T) { + a := TestAdvisoryIDs + _, err := NewTeeAdvisoryIDsString(a) require.Nil(t, err) } -func TestAdvisoryIDs_NewTeeAvisoryIDs_NOK(t *testing.T) { - expectedErr := "invalid type: int for AdvisoryIDs at index: 0" - a := make([]any, len(TestAdvisoryIDs)) - for i := range TestAdvisoryIDs { - a[i] = i - } - _, err := NewTeeAvisoryIDs(a) +func TestAdvisoryIDs_NewTeeAvisoryIDsExpr_NOK(t *testing.T) { + expectedErr := "invalid operator : 5" + a := make([]string, len(TestAdvisoryIDs)) + _, err := NewTeeAdvisoryIDsExpr(NOP, a) assert.EqualError(t, err, expectedErr) } -func TestAdvisoryIDs_AddAdvisoryIDs_OK(t *testing.T) { - a := initAdvisoryIDs() - adv := TeeAdvisoryIDs{} - err := adv.AddTeeAdvisoryIDs(a) +func TestAdvisoryIDs_AddAdvisoryIDsString_OK(t *testing.T) { + a := TestAdvisoryIDs + adv := &TeeAdvisoryIDs{val: a} + _, err := adv.AddTeeAdvisoryIDs(NOP, []string{"abcd"}) require.NoError(t, err) } -func TestAdvisoryIDs_AddAdvisoryIDs_NOK(t *testing.T) { - expectedErr := "invalid type: float64 for AdvisoryIDs at index: 0" - s := make([]any, len(TestInvalidAdvisoryIDs)) - for i := range TestInvalidAdvisoryIDs { - s[i] = TestInvalidAdvisoryIDs[i] - } - adv := TeeAdvisoryIDs{} - err := adv.AddTeeAdvisoryIDs(s) - assert.EqualError(t, err, expectedErr) +func TestAdvisoryIDs_AddAdvisoryIDsExpr_OK(t *testing.T) { + a := TestAdvisoryIDs + adv := TeeAdvisoryIDs{val: TaggedSetStringExpression{SetOperator: MEM, SetString: a}} + _, err := adv.AddTeeAdvisoryIDs(MEM, []string{"abcd"}) + require.NoError(t, err) } func TestAdvisoryIDs_Valid_OK(t *testing.T) { - a := initAdvisoryIDs() - adv, err := NewTeeAvisoryIDs(a) + a := TestAdvisoryIDs + ta := TeeAdvisoryIDs{val: a} + err := ta.Valid() require.NoError(t, err) - err = adv.Valid() + ta = TeeAdvisoryIDs{val: TaggedSetStringExpression{SetOperator: MEM, SetString: a}} + err = ta.Valid() require.NoError(t, err) } func TestAdvisoryIDs_Valid_NOK(t *testing.T) { - expectedErr := "empty AdvisoryIDs" - adv := TeeAdvisoryIDs{} - err := adv.Valid() + expectedErr := "TeeAdvisoryID not set" + a := []string{} + ta := TeeAdvisoryIDs{val: a} + err := ta.Valid() assert.EqualError(t, err, expectedErr) - - expectedErr = "invalid type: float64 for AdvisoryIDs at index: 0" - s := make([]any, len(TestInvalidAdvisoryIDs)) - for i := range TestInvalidAdvisoryIDs { - s[i] = TestInvalidAdvisoryIDs[i] - } - adv = TeeAdvisoryIDs(s) - err = adv.Valid() + expectedErr = "unknown type tdx.SetStringExpression for TeeAdvisoryIDs" + ta = TeeAdvisoryIDs{val: SetStringExpression{SetOperator: NOP, SetString: []string{"abc"}}} + err = ta.Valid() assert.EqualError(t, err, expectedErr) +} + +func TestAdvisoryIDs_JSON(t *testing.T) { + a := TestAdvisoryIDs + + for _, tv := range []struct { + input []string + ExpectedBytes []byte + }{ + { + input: []string{"SA-123"}, + ExpectedBytes: []byte(`{"type":"string","value":["SA-123"]}`), + }, + { + input: a, + ExpectedBytes: []byte(`{"type":"string","value":["SA-00078","SA-00077","SA-00079"]}`), + }, + } { + t.Run("test", func(t *testing.T) { + ta, err := NewTeeAdvisoryIDsString(tv.input) + require.NoError(t, err) + + data, err := ta.MarshalJSON() + require.NoError(t, err) + fmt.Printf("received string %s", string(data)) + assert.Equal(t, tv.ExpectedBytes, data) + + out := &TeeAdvisoryIDs{} + err = out.UnmarshalJSON(data) + require.NoError(t, err) + assert.Equal(t, tv.input, out.val) + }) + } } diff --git a/comid/tdx-profile/teedigest.go b/comid/tdx-profile/teedigest.go new file mode 100644 index 00000000..827d57da --- /dev/null +++ b/comid/tdx-profile/teedigest.go @@ -0,0 +1,188 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// nolint:dupl +package tdx + +import ( + "encoding/json" + "fmt" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/encoding" +) + +type Digests = comid.Digests + +// TeeDigest, holds the digests. Allowed values are an array of digests OR +// a digest expression +type TeeDigest struct { + val interface{} +} + +// NewTeeDigest create a new TeeDigest from the +// supplied Digests and returns a pointer to +// the TeeDigest. +func NewTeeDigest(val Digests) (*TeeDigest, error) { + if len(val) == 0 { + return nil, fmt.Errorf("zero len value for TeeDigest") + } + return &TeeDigest{val: val}, nil +} + +// NewTeeDigestExpr create a new TeeDigest (that holds Digest Expression) from the +// supplied operator and Digests and returns a pointer to +// the TeeDigest. +func NewTeeDigestExpr(op uint, val Digests) (*TeeDigest, error) { + if len(val) == 0 { + return nil, fmt.Errorf("zero len value for TeeDigestExpr") + } + switch op { + case MEM, NMEM: // Allowed operators are either Members or Non Members + set, err := NewTaggedSetDigestExpression(op, val) + if err != nil { + return nil, fmt.Errorf("zero len value for TeeDigestExpr") + } + return &TeeDigest{val: *set}, nil + default: + return nil, fmt.Errorf("invalid operator : %d", op) + } +} + +// AddTeeDigest add supplied Digests to existing TeeDigest +func (o *TeeDigest) AddTeeDigest(op uint, val Digests) (*TeeDigest, error) { + if len(val) == 0 { + return nil, fmt.Errorf("zero len value for TeeDigestExpr") + } + switch t := o.val.(type) { + case Digests: + t = append(t, val...) + o.val = t + case TaggedSetDigestExpression: + if t.SetOperator != Operator(op) { + return nil, fmt.Errorf("operator mis-match TeeDigest Op: %d, Input Op: %d", t.SetOperator, op) + } + for _, value := range val { + t.SetDigest = append(t.SetDigest, value) + } + o.val = t + } + return o, nil +} + +// Valid checks for validity of TeeDigest and +// returns an error, if invalid +func (o TeeDigest) Valid() error { + if o.val == nil { + return fmt.Errorf("TeeDigest not set") + } + switch t := o.val.(type) { + case Digests: + if len(t) == 0 { + return fmt.Errorf("TeeDigest not set") + } + case TaggedSetDigestExpression: + if t.SetOperator != MEM && t.SetOperator != NMEM { + return fmt.Errorf("invalid operator in a TeeDigest: %d", t.SetOperator) + } + if len(t.SetDigest) == 0 { + return fmt.Errorf("TeeDigest not set") + } + } + return nil +} + +// IsDigests returns true if TeeDigest is TeeDigest +func (o TeeDigest) IsDigests() bool { + return isType[Digests](o.val) +} + +// IsDigestExpr returns true if TeeDigest is DigestExpr +func (o TeeDigest) IsDigestExpr() bool { + return isType[TaggedSetDigestExpression](o.val) +} + +// GetDigestExpr returns a Digest Expression +func (o TeeDigest) GetDigestExpr() (*TaggedSetDigestExpression, error) { + if err := o.Valid(); err != nil { + return nil, fmt.Errorf("invalid TEEDigest: %w", err) + } + + switch t := o.val.(type) { + case TaggedSetDigestExpression: + return &t, nil + default: + return nil, fmt.Errorf("invalid type: %T", t) + } +} + +func (o TeeDigest) GetDigest() (Digests, error) { + switch t := o.val.(type) { + case Digests: + return t, nil + default: + return nil, fmt.Errorf("invalid type: %T", t) + } +} + +func (o TeeDigest) MarshalJSON() ([]byte, error) { + var ( + v encoding.TypeAndValue + b []byte + err error + ) + switch t := o.val.(type) { + case []string: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: DigestType, Value: b} + case TaggedSetDigestExpression: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: DigestExprType, Value: b} + default: + return nil, fmt.Errorf("unknown type %T for TeeDigest", t) + } + return json.Marshal(v) +} + +// UnmarshalJSON UnMarshals supplied JSON bytes to TeeDigest +func (o *TeeDigest) UnmarshalJSON(data []byte) error { + var v encoding.TypeAndValue + + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + switch v.Type { + case DigestType: + var x Digests + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeDigest of type string: %w", err) + } + o.val = x + case DigestExprType: + var x SetDigestExpression + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeDigest of type set expression: %w", err) + } + o.val = TaggedSetDigestExpression(x) + } + return nil +} + +// MarshalCBOR Marshals TeeDigest to CBOR +func (o TeeDigest) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.val) +} + +// UnmarshalCBOR UnMarshals supplied CBOR bytes to TeeDigest +func (o *TeeDigest) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.val) +} diff --git a/comid/tdx-profile/teedigest_test.go b/comid/tdx-profile/teedigest_test.go new file mode 100644 index 00000000..56208be7 --- /dev/null +++ b/comid/tdx-profile/teedigest_test.go @@ -0,0 +1,61 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" + "github.com/veraison/swid" +) + +func getNewDigests() Digests { + d := comid.NewDigests() + d.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) + return *d +} +func TestTeeDigest_NewTeeDigest_OK(t *testing.T) { + d := getNewDigests() + _, err := NewTeeDigest(d) + require.Nil(t, err) +} + +func TestTeeDigest_NewTeeDigestExpr_OK(t *testing.T) { + d := getNewDigests() + _, err := NewTeeDigestExpr(NMEM, d) + require.Nil(t, err) +} + +func TestTeeDigest_NewTeeDigestExpr_NOK(t *testing.T) { + expectedErr := "invalid operator : 1" + d := getNewDigests() + _, err := NewTeeDigestExpr(GT, d) + assert.EqualError(t, err, expectedErr) +} + +func TestTeeDigest_GetTeeDigest_OK(t *testing.T) { + d := getNewDigests() + dg := TeeDigest{val: d} + d1, err := dg.GetDigest() + require.NoError(t, err) + b := d.Equal(d1) + require.True(t, b) +} + +func TestTeeDigest_AddTeeDigest_OK(t *testing.T) { + d := getNewDigests() + dg := TeeDigest{val: d} + _, err := dg.AddTeeDigest(NOP, d) + require.NoError(t, err) +} + +func TestTeeDigest_AddTeeDigest_NOK(t *testing.T) { + expectedErr := "operator mis-match TeeDigest Op: 6, Input Op: 5" + d := getNewDigests() + dg := TeeDigest{TaggedSetDigestExpression{SetOperator: MEM, SetDigest: SetDigest(d)}} + _, err := dg.AddTeeDigest(NOP, d) + assert.EqualError(t, err, expectedErr) +} diff --git a/comid/tdx-profile/teeinstanceid.go b/comid/tdx-profile/teeinstanceid.go index 8d9cbae0..5575c045 100644 --- a/comid/tdx-profile/teeinstanceid.go +++ b/comid/tdx-profile/teeinstanceid.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" - "github.com/fxamacker/cbor/v2" "github.com/veraison/corim/encoding" ) @@ -150,14 +149,14 @@ func (o *TeeInstanceID) UnmarshalJSON(data []byte) error { } switch v.Type { - case "uint": + case UintType: var x uint if err := json.Unmarshal(v.Value, &x); err != nil { return fmt.Errorf( "cannot unmarshal TeeInstanceID of type uint: %w", err) } o.val = x - case "bytes": + case BytesType: var x []byte if err := json.Unmarshal(v.Value, &x); err != nil { return fmt.Errorf( @@ -170,10 +169,10 @@ func (o *TeeInstanceID) UnmarshalJSON(data []byte) error { // MarshalCBOR Marshals TeeInstanceID to CBOR func (o TeeInstanceID) MarshalCBOR() ([]byte, error) { - return cbor.Marshal(o.val) + return em.Marshal(o.val) } // UnmarshalCBOR UnMarshals supplied CBOR bytes to TeeInstanceID func (o *TeeInstanceID) UnmarshalCBOR(data []byte) error { - return cbor.Unmarshal(data, &o.val) + return dm.Unmarshal(data, &o.val) } diff --git a/comid/tdx-profile/teeisvproid.go b/comid/tdx-profile/teeisvproid.go index c8382d88..1ce75c57 100644 --- a/comid/tdx-profile/teeisvproid.go +++ b/comid/tdx-profile/teeisvproid.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" - "github.com/fxamacker/cbor/v2" "github.com/veraison/corim/encoding" ) @@ -128,13 +127,13 @@ func (o TeeISVProdID) MarshalJSON() ([]byte, error) { if err != nil { return nil, err } - v = encoding.TypeAndValue{Type: "uint", Value: b} + v = encoding.TypeAndValue{Type: UintType, Value: b} case []byte: b, err = json.Marshal(t) if err != nil { return nil, err } - v = encoding.TypeAndValue{Type: "bytes", Value: b} + v = encoding.TypeAndValue{Type: BytesType, Value: b} default: return nil, fmt.Errorf("unknown type %T for TeeISVProdID", t) } @@ -150,14 +149,14 @@ func (o *TeeISVProdID) UnmarshalJSON(data []byte) error { } switch v.Type { - case "uint": + case UintType: var x uint if err := json.Unmarshal(v.Value, &x); err != nil { return fmt.Errorf( "cannot unmarshal TeeISVProdID of type uint: %w", err) } o.val = x - case "bytes": + case BytesType: var x []byte if err := json.Unmarshal(v.Value, &x); err != nil { return fmt.Errorf( @@ -170,10 +169,10 @@ func (o *TeeISVProdID) UnmarshalJSON(data []byte) error { // MarshalCBOR Marshals TeeISVProdID to CBOR bytes func (o TeeISVProdID) MarshalCBOR() ([]byte, error) { - return cbor.Marshal(o.val) + return em.Marshal(o.val) } // UnmarshalCBOR UnMarshals supplied CBOR bytes to TeeISVProdID func (o *TeeISVProdID) UnmarshalCBOR(data []byte) error { - return cbor.Unmarshal(data, &o.val) + return dm.Unmarshal(data, &o.val) } diff --git a/comid/tdx-profile/teesvn.go b/comid/tdx-profile/teesvn.go new file mode 100644 index 00000000..7c24a5e7 --- /dev/null +++ b/comid/tdx-profile/teesvn.go @@ -0,0 +1,141 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "encoding/json" + "fmt" + + "github.com/veraison/corim/encoding" +) + +// TeeSVN holds a TeeSVN, supported formats are uint and TaggedNumericExpression +type TeeSVN struct { + val interface{} +} + +// NewSvnExpression creates a new TeeSVN which contains an SVN of type Numeric Expression +func NewSvnExpression(val uint) (*TeeSVN, error) { + tnum, err := NewTaggedNumericExpression(GE, val) + if err != nil { + return nil, err + } + return &TeeSVN{val: *tnum}, nil +} + +// NewSvnUint creates a new TeeSVN which contains an SVN of type uint +func NewSvnUint(val uint) (*TeeSVN, error) { + return &TeeSVN{val: val}, nil +} + +func (o TeeSVN) Valid() error { + switch t := o.val.(type) { + case uint, uint64: + return nil + case TaggedNumericExpression: + if t.NumericOperator != GE { + return fmt.Errorf("unknown operator %s for Numeric TeeSVN", NumericOperatorToString[t.NumericOperator]) + } + exp := t.NumericType + switch k := exp.val.(type) { + case uint, uint64: + return nil + default: + return fmt.Errorf("unknown type %T for Numeric TeeSVN", k) + } + default: + return fmt.Errorf("unknown type %T for TeeSVN", t) + } +} + +func (o TeeSVN) IsExpression() bool { + return isType[TaggedNumericExpression](o.val) +} + +func (o TeeSVN) IsUint() bool { + return isType[uint](o.val) || isType[uint64](o.val) +} + +func (o TeeSVN) GetUint() (uint, error) { + switch t := o.val.(type) { + case uint: + return t, nil + case uint64: + return uint(t), nil + default: + return 0, fmt.Errorf("unknown type %T for TeeSVN", t) + } +} + +func (o TeeSVN) GetNumericExpression() (TaggedNumericExpression, error) { + switch t := o.val.(type) { + case TaggedNumericExpression: + return t, nil + default: + return TaggedNumericExpression{}, fmt.Errorf("unknown type %T for TeeSVN", t) + } +} + +// MarshalJSON Marshals TeeSVN to JSON bytes +func (o TeeSVN) MarshalJSON() ([]byte, error) { + var ( + v encoding.TypeAndValue + b []byte + err error + ) + switch t := o.val.(type) { + case uint, uint64: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: UintType, Value: b} + case TaggedNumericExpression: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: NumericExprType, Value: b} + default: + return nil, fmt.Errorf("unknown type %T for TeeSVN", t) + } + return json.Marshal(v) +} + +// UnmarshalJSON UnMarshals supplied JSON bytes to TeeSVN +func (o *TeeSVN) UnmarshalJSON(data []byte) error { + var v encoding.TypeAndValue + + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + switch v.Type { + case UintType: + var x uint + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeInstanceID of type uint: %w", err) + } + o.val = x + case NumericExprType: + var x NumericExpression + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeSVN of type numeric-expression: %w", err) + } + o.val = TaggedNumericExpression(x) + } + return o.Valid() +} + +// MarshalCBOR Marshals TeeSVN to CBOR +func (o TeeSVN) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.val) +} + +// UnmarshalCBOR UnMarshals supplied CBOR bytes to TeeSVN +func (o *TeeSVN) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.val) +} diff --git a/comid/tdx-profile/teetcbcompsvn.go b/comid/tdx-profile/teetcbcompsvn.go index ec28fe56..ef607aff 100644 --- a/comid/tdx-profile/teetcbcompsvn.go +++ b/comid/tdx-profile/teetcbcompsvn.go @@ -3,14 +3,18 @@ package tdx -import "fmt" +import ( + "fmt" +) // MaxSVNCount is the maximum SVN count in TeeTcbCompSvn const MaxSVNCount = 16 type TeeTcbCompSvn [MaxSVNCount]TeeSVN -func NewTeeTcbCompSVN(val []uint) (*TeeTcbCompSvn, error) { +// NewTeeTcbCompSvnExpression creates a new TeeTcbCompSvn with an array of SVN as Numeric Expression +// from the supplied array of integers +func NewTeeTcbCompSvnExpression(val []uint) (*TeeTcbCompSvn, error) { if len(val) > MaxSVNCount { return nil, fmt.Errorf("invalid length %d for TeeTcbCompSVN", len(val)) } else if len(val) == 0 { @@ -19,7 +23,26 @@ func NewTeeTcbCompSVN(val []uint) (*TeeTcbCompSvn, error) { TeeTcbCompSVN := make([]TeeSVN, MaxSVNCount) for i, value := range val { - TeeTcbCompSVN[i] = TeeSVN(value) + svn, err := NewSvnExpression(value) + if err != nil { + return nil, fmt.Errorf("unable to get New SVN Numeric: %w", err) + } + TeeTcbCompSVN[i] = *svn + } + return (*TeeTcbCompSvn)(TeeTcbCompSVN), nil +} + +// NewTeeTcbSVNUint creates a new TeeTcbCompSvn with an array of SVN of type unsigned integers +func NewTeeTcbCompSvnUint(val []uint) (*TeeTcbCompSvn, error) { + if len(val) > MaxSVNCount { + return nil, fmt.Errorf("invalid length %d for TeeTcbCompSVN", len(val)) + } else if len(val) == 0 { + return nil, fmt.Errorf("no value supplied for TeeTcbCompSVN") + } + + TeeTcbCompSVN := make([]TeeSVN, MaxSVNCount) + for i, value := range val { + TeeTcbCompSVN[i].val = value } return (*TeeTcbCompSvn)(TeeTcbCompSVN), nil } @@ -32,5 +55,11 @@ func (o TeeTcbCompSvn) Valid() error { if len(o) > MaxSVNCount { return fmt.Errorf("invalid length: %d for TeeTcbCompSVN", len(o)) } + + for i, svn := range o { + if err := svn.Valid(); err != nil { + return fmt.Errorf("invalid TeeSvn at index %d, %w", i, err) + } + } return nil } diff --git a/comid/tdx-profile/teetcbcompsvn_test.go b/comid/tdx-profile/teetcbcompsvn_test.go new file mode 100644 index 00000000..69c488a6 --- /dev/null +++ b/comid/tdx-profile/teetcbcompsvn_test.go @@ -0,0 +1,61 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package tdx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTeeTcbCompSvn_NewTeeTcbCompSvnUint_OK(t *testing.T) { + _, err := NewTeeTcbCompSvnUint(TestCompSvn) + require.NoError(t, err) +} + +func TestTeeTcbCompSvn_NewTeeTcbCompSvnUint_NOK(t *testing.T) { + expectedErr := "no value supplied for TeeTcbCompSVN" + var val []uint + _, err := NewTeeTcbCompSvnUint(val) + assert.EqualError(t, err, expectedErr) + expectedErr = "invalid length 18 for TeeTcbCompSVN" + v := make([]uint, 18) + _, err = NewTeeTcbCompSvnUint(v) + assert.EqualError(t, err, expectedErr) +} + +func TestTeeTcbCompSvn_NewTeeTcbCompSvnExpression_OK(t *testing.T) { + _, err := NewTeeTcbCompSvnExpression(TestCompSvn) + require.NoError(t, err) +} + +func TestTeeTcbCompSvn_NewTeeTcbCompSvnExpression_NOK(t *testing.T) { + expectedErr := "no value supplied for TeeTcbCompSVN" + var val []uint + _, err := NewTeeTcbCompSvnExpression(val) + assert.EqualError(t, err, expectedErr) + expectedErr = "invalid length 18 for TeeTcbCompSVN" + v := make([]uint, 18) + _, err = NewTeeTcbCompSvnExpression(v) + assert.EqualError(t, err, expectedErr) +} + +func TestTeeTcbCompSvn_Valid_OK(t *testing.T) { + tc, err := NewTeeTcbCompSvnExpression(TestCompSvn) + require.NoError(t, err) + err = tc.Valid() + require.NoError(t, err) + tc, err = NewTeeTcbCompSvnUint(TestCompSvn) + require.NoError(t, err) + err = tc.Valid() + require.NoError(t, err) +} + +func TestTeeTcbCompSvn_Valid_NOK(t *testing.T) { + expectedErr := "invalid TeeSvn at index 0, unknown type for TeeSVN" + var tc TeeTcbCompSvn + err := tc.Valid() + assert.EqualError(t, err, expectedErr) +} diff --git a/comid/tdx-profile/teetcbstatus.go b/comid/tdx-profile/teetcbstatus.go index 4a65c819..f5ab1020 100644 --- a/comid/tdx-profile/teetcbstatus.go +++ b/comid/tdx-profile/teetcbstatus.go @@ -4,51 +4,150 @@ // nolint:dupl package tdx -import "fmt" +import ( + "encoding/json" + "fmt" -type TeeTcbStatus setType + "github.com/veraison/corim/encoding" +) -func NewTeeTcbStatus(val []any) (*TeeTcbStatus, error) { - var ts TeeTcbStatus +// TeeTcbStatus, holds the TCB Status. Allowed values are an array of strings OR +// An array of strings expressed in an expression +type TeeTcbStatus struct { + val interface{} +} + +// NewTcbStatusExpr creates a new TeeTcbStatus from the supplied operator and +// an array of strings +func NewTcbStatusExpr(op Operator, val []string) (*TeeTcbStatus, error) { + if len(val) == 0 { + return nil, fmt.Errorf("zero len value for TeeTcbStatus") + } + switch op { + case MEM, NMEM: + set := SetStringExpression{SetOperator: op, SetString: val} + return &TeeTcbStatus{val: TaggedSetStringExpression(set)}, nil + default: + return nil, fmt.Errorf("invalid operator : %d", op) + } + +} + +// NewTeeTcbStatusString creates a new TeeTcbStatus from the +// supplied array of strings +func NewTeeTcbStatusString(val []string) (*TeeTcbStatus, error) { if len(val) == 0 { - return nil, fmt.Errorf("nil value argument") + return nil, fmt.Errorf("zero len value for TeeTcbStatus") } + return &TeeTcbStatus{val: val}, nil +} - for i, v := range val { - switch t := v.(type) { - case string: - ts = append(ts, t) - default: - return nil, fmt.Errorf("invalid type: %T for tcb status at index: %d", t, i) +// AddTeeTcbStatus add supplied TeeTcbStatus to existing TeeTcbStatus +func (o *TeeTcbStatus) AddTeeTcbStatus(op Operator, val []string) (*TeeTcbStatus, error) { + if len(val) == 0 { + return nil, fmt.Errorf("zero len value for TeeTcbStatus") + } + switch t := o.val.(type) { + case []string: + t = append(t, val...) + o.val = t + case TaggedSetStringExpression: + if t.SetOperator != op { + return nil, fmt.Errorf("operator mis-match TeeTcbStatus Op: %d, Input Op: %d", t.SetOperator, op) } + for _, value := range val { + t.SetString = append(t.SetString, value) + } + o.val = t } - return &ts, nil + return o, nil } -func (o *TeeTcbStatus) AddTeeTcbStatus(val []any) error { - for i, v := range val { - switch t := v.(type) { - case string: - *o = append(*o, t) - default: - return fmt.Errorf("invalid type: %T for tcb status at index: %d", t, i) +// Valid checks for validity of TeeTcbStatus and +// returns an error, if invalid +func (o TeeTcbStatus) Valid() error { + if o.val == nil { + return fmt.Errorf("TeeTcbStatus not set") + } + switch t := o.val.(type) { + case []string: + if len(t) == 0 { + return fmt.Errorf("TeeTcbStatus not set") } + case TaggedSetStringExpression: + if t.SetOperator != MEM && t.SetOperator != NMEM { + return fmt.Errorf("invalid operator in a TeeTcbStatus: %d", t.SetOperator) + } + if len(t.SetString) == 0 { + return fmt.Errorf("TeeTcbStatus not set") + } + default: + return fmt.Errorf("unknown type %T for TeeTcbStatus", t) } return nil } -func (o TeeTcbStatus) Valid() error { - if len(o) == 0 { - return fmt.Errorf("empty tcb status") +// MarshalJSON marshals the TeeTcbStatus to JSON +func (o TeeTcbStatus) MarshalJSON() ([]byte, error) { + var ( + v encoding.TypeAndValue + b []byte + err error + ) + switch t := o.val.(type) { + case []string: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: StringType, Value: b} + case TaggedSetStringExpression: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = encoding.TypeAndValue{Type: StringExprType, Value: b} + default: + return nil, fmt.Errorf("unknown type %T for TeeTcbStatus", t) + } + return json.Marshal(v) +} + +// UnmarshalJSON Unmarshals supplied JSON bytes to TeeTcbStatus +func (o *TeeTcbStatus) UnmarshalJSON(data []byte) error { + var v encoding.TypeAndValue + + if err := json.Unmarshal(data, &v); err != nil { + return err } - for i, v := range o { - switch t := v.(type) { - case string: - continue - default: - return fmt.Errorf("invalid type: %T for tcb status at index: %d", t, i) + switch v.Type { + case StringType: + var x uint + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeTcbStatus of type string: %w", err) } + o.val = x + case StringExprType: + var x SetStringExpression + if err := json.Unmarshal(v.Value, &x); err != nil { + return fmt.Errorf( + "cannot unmarshal TeeTcbStatus of type set expression: %w", err) + } + o.val = TaggedSetStringExpression(x) + default: + return fmt.Errorf("unknown type %s for TeeTcbStatus", v.Type) } return nil } + +// MarshalCBOR marshals TeeTcbStatus to CBOR +func (o TeeTcbStatus) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.val) +} + +// UnmarshalCBOR Unmarshals supplied CBOR bytes to TeeTcbStatus +func (o *TeeTcbStatus) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.val) +} diff --git a/comid/tdx-profile/teetcbstatus_test.go b/comid/tdx-profile/teetcbstatus_test.go index 15dc398d..2c01df3e 100644 --- a/comid/tdx-profile/teetcbstatus_test.go +++ b/comid/tdx-profile/teetcbstatus_test.go @@ -10,71 +10,63 @@ import ( "github.com/stretchr/testify/require" ) -func initTcbStatus() []any { - s := make([]any, len(TestTCBStatus)) - for i := range TestTCBStatus { - s[i] = TestTCBStatus[i] - } - return s +func TestTcbStatus_NewTeeTcbStatusString_OK(t *testing.T) { + s := TestTCBStatus + _, err := NewTeeTcbStatusString(s) + require.NoError(t, err) } -func TestTcbStatus_NewTeeTcbStatus_OK(t *testing.T) { - s := initTcbStatus() - _, err := NewTeeTcbStatus(s) +func TestTcbStatus_NewTeeTcbStatusExpr_OK(t *testing.T) { + s := TestTCBStatus + _, err := NewTcbStatusExpr(MEM, s) require.NoError(t, err) } -func TestTcbStatus_NewTeeTcbStatus_NOK(t *testing.T) { - s := make([]any, len(TestTCBStatus)) - for i := range TestTCBStatus { - s[i] = i - } - expectedErr := "invalid type: int for tcb status at index: 0" - _, err := NewTeeTcbStatus(s) +func TestTcbStatus_NewTeeTcbStatusString_NOK(t *testing.T) { + expectedErr := "zero len value for TeeTcbStatus" + s := []string{} + _, err := NewTeeTcbStatusString(s) assert.EqualError(t, err, expectedErr) - var m []any - expectedErr = "nil value argument" - _, err = NewTeeTcbStatus(m) +} + +func TestTcbStatus_NewTeeTcbStatusExpr_NOK(t *testing.T) { + expectedErr := "invalid operator : 5" + s := TestTCBStatus + _, err := NewTcbStatusExpr(NOP, s) assert.EqualError(t, err, expectedErr) } func TestTcbStatus_AddTcbStatus_OK(t *testing.T) { - s := initTcbStatus() - status := TeeTcbStatus{} - err := status.AddTeeTcbStatus(s) + s := TestTCBStatus + status := TeeTcbStatus{val: []string{"abcd"}} + _, err := status.AddTeeTcbStatus(NOP, s) require.Nil(t, err) } func TestTcbStatus_AddTcbStatus_NOK(t *testing.T) { - expectedErr := "invalid type: int for tcb status at index: 0" - s := make([]any, len(TestInvalidTCBStatus)) - for i := range TestInvalidTCBStatus { - s[i] = TestInvalidTCBStatus[i] - } - status := TeeTcbStatus{} - err := status.AddTeeTcbStatus(s) + expectedErr := "operator mis-match TeeTcbStatus Op: 6, Input Op: 2" + s := TestTCBStatus + status, err := NewTcbStatusExpr(MEM, s) + require.Nil(t, err) + _, err = status.AddTeeTcbStatus(GE, []string{"abcd"}) assert.EqualError(t, err, expectedErr) } func TestTcbStatus_Valid_OK(t *testing.T) { - s := initTcbStatus() - status, err := NewTeeTcbStatus(s) + s := TestTCBStatus + status, err := NewTeeTcbStatusString(s) require.Nil(t, err) err = status.Valid() require.Nil(t, err) } func TestTcbStatus_Valid_NOK(t *testing.T) { - expectedErr := "empty tcb status" - status := TeeTcbStatus{} + expectedErr := "invalid operator in a TeeTcbStatus: 2" + status := TeeTcbStatus{val: TaggedSetStringExpression(SetStringExpression{SetOperator: 2, SetString: []string{"valid"}})} err := status.Valid() assert.EqualError(t, err, expectedErr) - expectedErr = "invalid type: int for tcb status at index: 0" - s := make([]any, len(TestInvalidTCBStatus)) - for i := range TestInvalidTCBStatus { - s[i] = TestInvalidTCBStatus[i] - } - status = TeeTcbStatus(s) + expectedErr = "unknown type []int for TeeTcbStatus" + status = TeeTcbStatus{val: TestInvalidTCBStatus} err = status.Valid() assert.EqualError(t, err, expectedErr) } diff --git a/comid/tdx-profile/test_vars.go b/comid/tdx-profile/test_vars.go index 4e148c61..99294ecd 100644 --- a/comid/tdx-profile/test_vars.go +++ b/comid/tdx-profile/test_vars.go @@ -16,175 +16,383 @@ var ( TestTeeAttributes = []byte{0x01, 0x01} TestTeeMiscSelect = []byte{0x0B, 0x0C, 0x0D} TestPCEID = "PCEID001" - TestCompSVN = []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + TestCompSvn = []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} TestTCBStatus = []string{"OutOfDate", "ConfigurationNeeded", "UpToDate"} TestInvalidTCBStatus = []int{1, 2, 3} TestAdvisoryIDs = []string{"SA-00078", "SA-00077", "SA-00079"} TestInvalidAdvisoryIDs = []float64{1.234, 2.567} - TestISVSVN = 10 - TestTCBEvalNum = 11 + TestISVSVN = uint(10) + TestTCBEvalNum = uint(11) TestTime = "2025-01-29T00:00:00Z" TDXPCERefValTemplate = `{ - "lang": "en-GB", - "tag-identity": { - "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B17", - "version": 0 - }, - "entities": [ - { - "name": "INTEL", - "regid": "https://intel.com", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "oid", - "value": "2.16.840.1.113741.1.2.3.4.6" - }, - "vendor": "Intel Corporation", - "model": "0123456789ABCDEF" - } - }, - "measurements": [ - { - "value": { - "instanceid": { - "type": "uint", - "value": 11 - }, - "tcbcompsvn": [10, 10, 2, 2, 2, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "pceid": "0000" - }, - "authorized-by": { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B17", + "version": 0 + }, + "entities": [ + { + "name": "INTEL", + "regid": "https://intel.com", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.16.840.1.113741.1.2.3.4.6" + }, + "vendor": "Intel Corporation", + "model": "0123456789ABCDEF" + } + }, + "measurements": [ + { + "value": { + "instanceid": { + "type": "uint", + "value": 11 + }, + "tcbcompsvn": [ + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 2 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + } + ], + "pceid": "0000" + }, + "authorized-by": { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + } + } + ] } - } ] - } - ] - } + } } ` TDXQERefValTemplate = `{ - "lang": "en-GB", - "tag-identity": { - "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", - "version": 0 - }, - "entities": [ - { - "name": "INTEL", - "regid": "https://intel.com", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "oid", - "value": "2.16.840.1.113741.1.2.3.4.1" - }, - "vendor": "Intel Corporation", - "model": "TDX QE TCB" - } - }, - "measurements": [ - { - "value": { - "miscselect": "wAAAAPv/AAA=", - "tcbevalnum": 11, - "mrsigner": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", - "sha-512:oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" - ], - "isvprodid": { - "type": "bytes", - "value": "AwM=" - } - }, - "authorized-by": { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 0 + }, + "entities": [ + { + "name": "INTEL", + "regid": "https://intel.com", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.16.840.1.113741.1.2.3.4.1" + }, + "vendor": "Intel Corporation", + "model": "TDX QE TCB" + } + }, + "measurements": [ + { + "value": { + "miscselect": "wAAAAPv/AAA=", + "tcbevalnum": { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 11 + } + } + }, + "mrsigner": { + "type": "digest-expression", + "value": { + "set-operator": "member", + "set-digest": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", + "sha-512:oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" + ] + } + }, + "isvprodid": { + "type": "bytes", + "value": "AwM=" + } + }, + "authorized-by": { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + } + } + ] } - } ] - } - ] - } -} + } +} ` TDXSeamRefValJSONTemplate = `{ - "lang": "en-GB", - "tag-identity": { - "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B20", - "version": 0 - }, - "entities": [ - { - "name": "INTEL", - "regid": "https://intel.com", - "roles": [ - "tagCreator", - "creator", - "maintainer" - ] - } - ], - "triples": { - "reference-values": [ - { - "environment": { - "class": { - "id": { - "type": "oid", - "value": "2.16.840.1.113741.1.2.3.4.5" - }, - "vendor": "Intel Corporation", - "model": "TDX SEAM" - } - }, - "measurements": [ - { - "value": { - "isvprodid": { - "type": "bytes", - "value": "AwM=" - }, - "isvsvn": 10, - "attributes": "8AoL", - "tcbevalnum": 11, - "mrtee" : ["sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc="], - "mrsigner": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", - "sha-512:oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" - ] - }, - "authorized-by": { - "type": "pkix-base64-key", - "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B20", + "version": 0 + }, + "entities": [ + { + "name": "INTEL", + "regid": "https://intel.com", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.16.840.1.113741.1.2.3.4.5" + }, + "vendor": "Intel Corporation", + "model": "TDX SEAM" + } + }, + "measurements": [ + { + "value": { + "isvprodid": { + "type": "bytes", + "value": "AwM=" + }, + "isvsvn": { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 10 + } + } + }, + "attributes": "8AoL", + "tcbevalnum": { + "type": "numeric-expression", + "value": { + "numeric-operator": "greater_or_equal", + "numeric-type": { + "type": "uint", + "value": 11 + } + } + }, + "mrtee": { + "type": "digest-expression", + "value": { + "set-operator": "member", + "set-digest": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + ] + } + }, + "mrsigner": { + "type": "digest-expression", + "value": { + "set-operator": "member", + "set-digest": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", + "sha-512:oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" + ] + } + } + }, + "authorized-by": { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + } + } + ] } - } ] - } - ] - } + } } ` ) diff --git a/comid/tdx-profile/testcases/comid_pce_refval.cbor b/comid/tdx-profile/testcases/comid_pce_refval.cbor index f0b8c0621d0c59791e1bd0422dc0fde020c5f4b9..fc6ef7f2bad595fe88584347c6c974f3fba0abf1 100644 GIT binary patch delta 105 ncmeyzbdGt##`v4BvYMEM=t=KY)0FMP54qRXW0*eqvi{P8DN}AXv_GR1Ne3jM2#BC9A^HpgRTT`B= lUx=%ZZm^@Sfq{X!1uEClA{?x$F(b4fB;O^mBz5AiZ2z>% diff --git a/comid/tdx-profile/testcases/comid_seam_refval.cbor b/comid/tdx-profile/testcases/comid_seam_refval.cbor index 71e9618a33a5503e7c59b15d63f17ebfa8da3934..cbf4287108c470711ce42d01dd15781f3fa6a770 100644 GIT binary patch delta 46 wcmX@Xe1~~L4e!lYSxroA7C|>(l{B$UtXF3PGnytgs7yTV#tN35EXcSW0O8scD*ylh delta 25 hcmcb^e1dsG4KtfX(8Nx4#-@o~Did$HO;%;x4gh#J32p!Y diff --git a/comid/tdx-profile/testcases/src/comid_pce_refval.diag b/comid/tdx-profile/testcases/src/comid_pce_refval.diag index e40b8bd7..f6dd910a 100644 --- a/comid/tdx-profile/testcases/src/comid_pce_refval.diag +++ b/comid/tdx-profile/testcases/src/comid_pce_refval.diag @@ -23,7 +23,24 @@ / measurement-map / { / comid.mval / 1 : { / comid.instanceid / -77 : h'00112233445566778899aabbccddeeff', - / tcb-comp-svn / -125 : [ 10, 10, 2, 2, 2, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], + / tcb-comp-svn / -125 : [ + 60010([2,10]), + 60010([2,10]), + 60010([2,2]), + 60010([2,2]), + 60010([2,2]), + 60010([2,1]), + 60010([2,4]), + 60010([2,0]), + 60010([2,0]), + 60010([2,0]), + 60010([2,0]), + 60010([2,0]), + 60010([2,0]), + 60010([2,0]), + 60010([2,0]), + 60010([2,0]) + ], / pceid / -80 : "0000" }, / authorized-by / 2: 554("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----") diff --git a/comid/tdx-profile/testcases/src/comid_qe_refval.diag b/comid/tdx-profile/testcases/src/comid_qe_refval.diag index bf060279..090c72be 100644 --- a/comid/tdx-profile/testcases/src/comid_qe_refval.diag +++ b/comid/tdx-profile/testcases/src/comid_qe_refval.diag @@ -24,17 +24,21 @@ / comid.mval / 1 : { / comid.miscselect / -81 :h'A0B0C0D000000000', / comid.isvprodid / -85 : 1, - / comid.mrsigner / -84 : [ - [ - / alg-id / 1, / sha256 / - / digest / h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A' - ], - [ - / alg-id / 8, / sha384 / - / digest / h'a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a' - ] - ], - /comid.tcbevalnum / -86 : 11 +/ mrsigner / -84 : 60020([ / op.member / 6, + / digests-type / [ + [ + / alg-id / 1, / sha256 / + / digest / h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A' + ], + [ + / alg-id / 8, / sha384 / + / digest / h'a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a' + ] + ] + ]), + / tcb-eval-num / -86 : 60010([ / op.ge / 2, 11 ]), + / advisory-ids / -89 : 60021([ /member/ 6, [ "INTEL-SA-00078", "INTEL-SA-00079" ]]), + / tcbstatus / -88 : 60021([ /member/ 6, [ "UpToDate" ]]) }, / authorized-by / 2: 554("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----") } diff --git a/comid/tdx-profile/testcases/src/comid_seam_refval.diag b/comid/tdx-profile/testcases/src/comid_seam_refval.diag index 32cfe5cc..6f51a828 100644 --- a/comid/tdx-profile/testcases/src/comid_seam_refval.diag +++ b/comid/tdx-profile/testcases/src/comid_seam_refval.diag @@ -24,14 +24,17 @@ / comid.mval / 1 : { / comid.attributes / -82 :[ 1, 2], / comid.isvprodid / -85 : h'ABCD', - / comid.isvsvn / -73 : 6, - / comid.mrtee / -83 : [ + / comid.isvsvn / -73 : 60010([ / op.ge / 2, 6 ]), + / comid.mrtee / -83 : 60020([ / op.member / 6, + [ [ / alg-id / 1, / sha256 / / digest / h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A' ] - ], - / comid.mrsigner / -84 : [ + ] + ]), + / comid.mrsigner / -84 : 60020([ / op.member / 6, + [ [ / alg-id / 1, / sha256 / / digest / h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A' @@ -40,8 +43,9 @@ / alg-id / 8, / sha384 / / digest / h'a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6aa314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a' ] - ], - /comid.tcbevalnum / -86 : 11 + ] + ]), + / tcb-eval-num / -86 : 60010([ / op.ge / 2, 11 ]) }, 2: 554("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----") } diff --git a/comid/tdx-profile/types.go b/comid/tdx-profile/types.go index faddb11d..30a2f511 100644 --- a/comid/tdx-profile/types.go +++ b/comid/tdx-profile/types.go @@ -3,12 +3,16 @@ package tdx -import "github.com/veraison/corim/comid" - -type setType []any +const ( + UintType = "uint" + IntType = "int" + FloatType = "float" + StringType = "string" + StringExprType = "string-expression" + NumericExprType = "numeric-expression" + BytesType = "bytes" + DigestType = "digest" + DigestExprType = "digest-expression" +) type maskType []byte -type TeeSVN uint -type TeeDigest comid.Digests - -type TeeTcbEvalNum uint From 59105a88ad855e2354580e62992946fa1ab639c5 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 12 Jun 2025 14:11:03 +0100 Subject: [PATCH 098/110] fix!: unsigned CoRIMs always tagged Update ToCBOR/FromCBOR for UnsignedCorim to add/expect tag 501. Current spec requires that unsigned CoRIMs are always tagged with tag 501, both, when appearing as stand-alone CBOR documents, and when embedded as payloads of signed CoRIMs. Note: There was already TaggedUnsignedCorim type registered under tagged 501, however it was not being used and its marshalling didn't work correctly due to the custom To/FromCBOR in UnsignedCorim used in lieu of the standard marshalling mechanism. Rather than (a) fixing its serialization, (b) forwarding all UnsignedCorim methods, and (c) replacing UnsignedCorim usaged with TaggedUnsignedCorim all over the place, TaggedUnsignedCorim was simply removed, and tagging was added to CBOR serialization of UnsignedCorim, as it's not really valid for it to appear untagged, and this minimizes API impact (and is also consistent with SignedCorim that serializes tagged). BREAKING CHANGE: serialized unsigned CoRIMs now start with tag 501. (This is an ABI-only break; all APIs remain the same). Signed-off-by: Sergei Trofimov --- corim/cbor.go | 3 +- corim/example_profile_test.go | 2 +- corim/profiles.go | 8 +- corim/signedcorim_test.go | 86 +---------- corim/testcases/regen-from-src.sh | 2 +- .../signed-corim-with-extensions.cbor | Bin 681 -> 687 bytes corim/testcases/signed-example-corim.cbor | Bin 684 -> 690 bytes corim/testcases/signed-good-corim.cbor | Bin 607 -> 613 bytes .../testcases/src/corim-with-extensions.yaml | 134 +++++++++--------- corim/testcases/src/example-corim.yaml | 134 +++++++++--------- corim/testcases/src/good-corim.yaml | 122 ++++++++-------- .../unsigned-corim-with-extensions.cbor | Bin 514 -> 517 bytes corim/testcases/unsigned-example-corim.cbor | Bin 517 -> 520 bytes corim/testcases/unsigned-good-corim.cbor | Bin 440 -> 443 bytes corim/unsignedcorim.go | 16 ++- corim/unsignedcorim_test.go | 4 +- 16 files changed, 227 insertions(+), 284 deletions(-) diff --git a/corim/cbor.go b/corim/cbor.go index 75ed1757..3d9df1b9 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -22,8 +22,7 @@ var ( ComidTag = []byte{0xd9, 0x01, 0xfa} // 506() corimTagsMap = map[uint64]interface{}{ - 32: comid.TaggedURI(""), - 501: TaggedUnsignedCorim(UnsignedCorim{}), + 32: comid.TaggedURI(""), } ) diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index 89cf7242..a0dcda87 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -196,5 +196,5 @@ func Example_profile_marshal() { fmt.Printf("corim: %v", hex.EncodeToString(buf)) // output: - // corim: a300f6018158d9d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 + // corim: d901f5a300f6018158d9d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 } diff --git a/corim/profiles.go b/corim/profiles.go index 05a2682a..ca5d0759 100644 --- a/corim/profiles.go +++ b/corim/profiles.go @@ -3,7 +3,9 @@ package corim import ( + "bytes" "encoding/json" + "errors" "fmt" "reflect" @@ -74,11 +76,15 @@ func UnmarshalSignedCorimFromCBOR(buf []byte) (*SignedCorim, error) { // the data, they will be registered with the UnsignedCorim before it is // unmarshaled. func UnmarshalUnsignedCorimFromCBOR(buf []byte) (*UnsignedCorim, error) { + if !bytes.Equal(buf[:3], UnsignedCorimTag) { + return nil, errors.New("did not see unsigned CoRIM tag") + } + profiled := struct { Profile *eat.Profile `cbor:"3,keyasint,omitempty"` }{} - if err := dm.Unmarshal(buf, &profiled); err != nil { + if err := dm.Unmarshal(buf[3:], &profiled); err != nil { return nil, err } diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index eee2268a..cd82a7ac 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -112,82 +112,6 @@ var ( }`) ) -func TestSignedCorim_FromCOSE_ok(t *testing.T) { - /* - 18( - [ - / protected h'a10126' / << { - / alg / 1: -7, / ECDSA 256 / - / content-type / 3: "application/rim+cbor", - / issuer-key-id / 4: 'meriadoc.brandybuck@buckland.example', - / corim-meta / 8: h'a200a1006941434d45204c74642e01a101c11a5fad2056' - } >>, - / unprotected / {}, - / payload / << { - 0: "test corim id", - 1: [ - h'D901FAA40065656E2D474201A1005043BBE37F2E614B33AED353CFF1428B160281A3006941434D45204C74642E01D8207468747470733A2F2F61636D652E6578616D706C65028300010204A1008182A100A300D90258582061636D652D696D706C656D656E746174696F6E2D69642D303030303030303031016441434D45026A526F616452756E6E657283A200D90258A30162424C0465322E312E30055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A102818201582087428FC522803D31065E7BCE3CF03FE475096631E5E07BBD7A0FDE60C4CF25C7A200D90258A3016450526F540465312E332E35055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A10281820158200263829989B6FD954F72BAAF2FC64BC2E2F01D692D4DE72986EA808F6E99813FA200D90258A3016441526F540465302E312E34055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A1028182015820A3A5E715F0CC574A73C3F9BEBB6BC24F32FFD5B67B387244C2C909DA779A1478' - ] - } >>, - / signature / h'deadbeef' - ] - ) - */ - tv := []byte{ - 0xd2, 0x84, 0x58, 0x59, 0xa4, 0x01, 0x26, 0x03, 0x74, 0x61, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x72, 0x69, 0x6d, - 0x2b, 0x63, 0x62, 0x6f, 0x72, 0x04, 0x58, 0x24, 0x6d, 0x65, 0x72, 0x69, - 0x61, 0x64, 0x6f, 0x63, 0x2e, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x79, 0x62, - 0x75, 0x63, 0x6b, 0x40, 0x62, 0x75, 0x63, 0x6b, 0x6c, 0x61, 0x6e, 0x64, - 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x08, 0x57, 0xa2, 0x00, - 0xa1, 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x4c, 0x74, 0x64, 0x2e, - 0x01, 0xa1, 0x01, 0xc1, 0x1a, 0x5f, 0xad, 0x20, 0x56, 0xa0, 0x59, 0x01, - 0xb8, 0xa2, 0x00, 0x6d, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x72, - 0x69, 0x6d, 0x20, 0x69, 0x64, 0x01, 0x81, 0x59, 0x01, 0xa3, 0xd9, 0x01, - 0xfa, 0xa4, 0x00, 0x65, 0x65, 0x6e, 0x2d, 0x47, 0x42, 0x01, 0xa1, 0x00, - 0x50, 0x43, 0xbb, 0xe3, 0x7f, 0x2e, 0x61, 0x4b, 0x33, 0xae, 0xd3, 0x53, - 0xcf, 0xf1, 0x42, 0x8b, 0x16, 0x02, 0x81, 0xa3, 0x00, 0x69, 0x41, 0x43, - 0x4d, 0x45, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x01, 0xd8, 0x20, 0x74, 0x68, - 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x63, 0x6d, 0x65, 0x2e, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x83, 0x00, 0x01, 0x02, - 0x04, 0xa1, 0x00, 0x81, 0x82, 0xa1, 0x00, 0xa3, 0x00, 0xd9, 0x02, 0x58, - 0x58, 0x20, 0x61, 0x63, 0x6d, 0x65, 0x2d, 0x69, 0x6d, 0x70, 0x6c, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x64, - 0x2d, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x01, 0x64, - 0x41, 0x43, 0x4d, 0x45, 0x02, 0x6a, 0x52, 0x6f, 0x61, 0x64, 0x52, 0x75, - 0x6e, 0x6e, 0x65, 0x72, 0x83, 0xa2, 0x00, 0xd9, 0x02, 0x58, 0xa3, 0x01, - 0x62, 0x42, 0x4c, 0x04, 0x65, 0x32, 0x2e, 0x31, 0x2e, 0x30, 0x05, 0x58, - 0x20, 0xac, 0xbb, 0x11, 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, 0x52, 0x3c, - 0xe4, 0xce, 0x1a, 0x24, 0x5a, 0xe1, 0xa2, 0x39, 0xae, 0x3c, 0x6b, 0xfd, - 0x9e, 0x78, 0x71, 0xf7, 0xe5, 0xd8, 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, - 0x81, 0x82, 0x01, 0x58, 0x20, 0x87, 0x42, 0x8f, 0xc5, 0x22, 0x80, 0x3d, - 0x31, 0x06, 0x5e, 0x7b, 0xce, 0x3c, 0xf0, 0x3f, 0xe4, 0x75, 0x09, 0x66, - 0x31, 0xe5, 0xe0, 0x7b, 0xbd, 0x7a, 0x0f, 0xde, 0x60, 0xc4, 0xcf, 0x25, - 0xc7, 0xa2, 0x00, 0xd9, 0x02, 0x58, 0xa3, 0x01, 0x64, 0x50, 0x52, 0x6f, - 0x54, 0x04, 0x65, 0x31, 0x2e, 0x33, 0x2e, 0x35, 0x05, 0x58, 0x20, 0xac, - 0xbb, 0x11, 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, 0x52, 0x3c, 0xe4, 0xce, - 0x1a, 0x24, 0x5a, 0xe1, 0xa2, 0x39, 0xae, 0x3c, 0x6b, 0xfd, 0x9e, 0x78, - 0x71, 0xf7, 0xe5, 0xd8, 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, 0x81, 0x82, - 0x01, 0x58, 0x20, 0x02, 0x63, 0x82, 0x99, 0x89, 0xb6, 0xfd, 0x95, 0x4f, - 0x72, 0xba, 0xaf, 0x2f, 0xc6, 0x4b, 0xc2, 0xe2, 0xf0, 0x1d, 0x69, 0x2d, - 0x4d, 0xe7, 0x29, 0x86, 0xea, 0x80, 0x8f, 0x6e, 0x99, 0x81, 0x3f, 0xa2, - 0x00, 0xd9, 0x02, 0x58, 0xa3, 0x01, 0x64, 0x41, 0x52, 0x6f, 0x54, 0x04, - 0x65, 0x30, 0x2e, 0x31, 0x2e, 0x34, 0x05, 0x58, 0x20, 0xac, 0xbb, 0x11, - 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, 0x52, 0x3c, 0xe4, 0xce, 0x1a, 0x24, - 0x5a, 0xe1, 0xa2, 0x39, 0xae, 0x3c, 0x6b, 0xfd, 0x9e, 0x78, 0x71, 0xf7, - 0xe5, 0xd8, 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, - 0x20, 0xa3, 0xa5, 0xe7, 0x15, 0xf0, 0xcc, 0x57, 0x4a, 0x73, 0xc3, 0xf9, - 0xbe, 0xbb, 0x6b, 0xc2, 0x4f, 0x32, 0xff, 0xd5, 0xb6, 0x7b, 0x38, 0x72, - 0x44, 0xc2, 0xc9, 0x09, 0xda, 0x77, 0x9a, 0x14, 0x78, 0x44, 0xde, 0xad, - 0xbe, 0xef, - } - - var actual SignedCorim - err := actual.FromCOSE(tv) - - assert.Nil(t, err) -} - func TestSignedCorim_TaggedFromCOSE_ok(t *testing.T) { /* 500( @@ -310,8 +234,8 @@ func TestSignedCorim_FromCOSE_fail_corim_bad_cbor(t *testing.T) { 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x72, 0x69, 0x6d, 0x2b, 0x63, 0x62, 0x6f, 0x72, 0x08, 0x57, 0xa2, 0x00, 0xa1, 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x01, 0xa1, 0x01, - 0xc1, 0x1a, 0x5f, 0xad, 0x20, 0x56, 0xa0, 0x44, 0xba, 0xdc, 0xb0, 0x30, - 0x44, 0xde, 0xad, 0xbe, 0xef, + 0xc1, 0x1a, 0x5f, 0xad, 0x20, 0x56, 0xa0, 0x47, 0xd9, 0x01, 0xf5, 0xba, + 0xdc, 0xb0, 0x30, 0x44, 0xde, 0xad, 0xbe, 0xef, } var actual SignedCorim @@ -342,9 +266,9 @@ func TestSignedCorim_FromCOSE_fail_invalid_corim(t *testing.T) { 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x72, 0x69, 0x6d, 0x2b, 0x63, 0x62, 0x6f, 0x72, 0x08, 0x57, 0xa2, 0x00, 0xa1, 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x01, 0xa1, 0x01, - 0xc1, 0x1a, 0x5f, 0xad, 0x20, 0x56, 0xa0, 0x50, 0xa1, 0x00, 0x6d, 0x69, - 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x63, 0x6f, 0x72, 0x69, 0x6d, - 0x44, 0xde, 0xad, 0xbe, 0xef, + 0xc1, 0x1a, 0x5f, 0xad, 0x20, 0x56, 0xa0, 0x53, 0xd9, 0x01, 0xf5, 0xa1, + 0x00, 0x6d, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x63, 0x6f, + 0x72, 0x69, 0x6d, 0x44, 0xde, 0xad, 0xbe, 0xef, } var actual SignedCorim diff --git a/corim/testcases/regen-from-src.sh b/corim/testcases/regen-from-src.sh index f119e952..abbb4618 100644 --- a/corim/testcases/regen-from-src.sh +++ b/corim/testcases/regen-from-src.sh @@ -7,7 +7,7 @@ GEN_TESTCASE=$(go env GOPATH)/bin/gen-testcase if [[ ! -f ${GEN_TESTCASE} ]]; then echo "installing gen-testcase" - go install github.com/veraison/gen-testcase@v0.0.1 + go install github.com/veraison/gen-testcase@v0.0.3 fi testcases=( diff --git a/corim/testcases/signed-corim-with-extensions.cbor b/corim/testcases/signed-corim-with-extensions.cbor index 4bfca682959be82d3ace80585e5e20241a5f7759..9988c0847b03576ad3098c0a83f2f76ec8d941ee 100644 GIT binary patch delta 234 zcmV*AaiMFZfS03AZulL0oWjPXmoUNb2=|CVPkD&E@gOOZE$R50ipoG8ez^z z00F@oX;f!`pjiS1*#Y&W0Bv+-b95kMZ*pmEAZcU+cpqr7Y9|2_0NDarqXA@4Qg2iQ zWic)@E;W+@0wj}?0WFjC0c1j0K;rG{Q{TRmY83G!{YuIuBvbZOEhpz?8?L8O0&YDP kUxL)!E6wh)+*cd5Wd^=FZ@fp$%V8Y4wTDRtxQQjg_pMM`T>t<8 delta 197 zcmV;$06PD#1*ruy(u7!DqX8xZbYXCCY-wX*bZKvHFLG&ZD`R4BatK&Kq5+}+!5U%C zNB{xB8fjE#fB>SAJQy1QbU{N+MIcOcWFT{CXKrb3XCP~3d7xPW0;I8fD*=<+0WJjq z*#cRkld%CS1!PcCZ&Z^B0%TDDZFFUGbRc7Ia%pWKX=GSH5G;sj$rIj$r@_MXSwG}? zp%xWbIOJ5~4y&LalI4=nfJsl!tixYqv0O;x43WWuZfW+zCM4_hnGRM%3(Pvjf>%wG diff --git a/corim/testcases/signed-example-corim.cbor b/corim/testcases/signed-example-corim.cbor index f6c4f46185582023dc04983cbef0118c99ed3c42..5749565e2207b6e76e55fadf72109215dc7b45d1 100644 GIT binary patch delta 201 zcmZ3(x{1}~QcFa_5=J%VlEi|7oXq6JlFa-({i4iV?c}8VA{IwOjtIv^jEfi!N+q82 zU|>8bl^K%WFj3KfV?iVn$4$nsOD4{LIe9Om9xKC5rpU#U|1qivr33`!hp?m?>KW^q zE`kVuXVm6RaRdr8E>e(6o5-6tS(!;~vOkmb#57B%2nUJJ=3G_}|L5y;Pb}u?d}h7) zRnKkSRGww$UX*e7J=gy#`diL?p40E^26KK##xC&?V?Ta>!z|1CPNs!BD)b#ZZ2{4b BO{xF@ delta 197 zcmdnQx`tKpQcFbaVn#LQlEi|7oXq6JlFa-({i4iV?c}8VB8~{hMH5X7L>U(`9F$5t z=fS{uP%1MdyPe%$O1olpn&9YN%(dXFBnR%H%LcU7kfy zDMz5x1#n>Sg8N!lsG;dX1IbbZ&(x3_mKh@X+2I(=FkW00b6 wfLLyc^<)R@cQxC)4Nt!giAXwE&bK<`lijwwr=^=V?6y!m^=X%d4A1fs04X9%%K!iX diff --git a/corim/testcases/signed-good-corim.cbor b/corim/testcases/signed-good-corim.cbor index 50dae69118fff2b0e11127df304879307902dcb1..da73cee72254476153377e0c851ebc18492ae355 100644 GIT binary patch delta 202 zcmcc5@{~p4QcFa_5=J%VlEi|7oXq6JlFa-({i4iV?c}8VA{Ix(iL&mjyKgdnT{N-O zd*Wve0fw7Qk&79VoP1bPjr0ul3?}O_Dl;yc9LT6KS%%S+cM({gCDi~ZX9AQ{VNG!i z$`6^mp3!acFGl4sre+34CWhRS)Z!9_pq^HV`DV&3ji=@ BO=tiB delta 196 zcmaFLa-T)^QcFbaVn#LQlEi|7oXq6JlFa-({i4iV?c}8VqKOLbf;$#5j;f4H(7QMVIY1;cX&ixy+^B42>?7kbZzpw3NbWE@8e*ge&rbtNu diff --git a/corim/testcases/src/corim-with-extensions.yaml b/corim/testcases/src/corim-with-extensions.yaml index 2f1a45f1..f07e38c0 100644 --- a/corim/testcases/src/corim-with-extensions.yaml +++ b/corim/testcases/src/corim-with-extensions.yaml @@ -3,73 +3,75 @@ # - extension claim -1 has been added to corim # - extension claim -1 has been added to comid entity --- -0: test corim id -3: http://example.com/test-profile --1: foo -1: -- encodedCBOR: - tag: 506 - value: - 0: en-GB - 1: - 0: !!binary |- - Q7vjfy5hSzOu01PP8UKLFg== - 2: - - 0: ACME Ltd. +tag: 501 +value: + 0: test corim id + 3: http://example.com/test-profile + -1: foo + 1: + - encodedCBOR: + tag: 506 + value: + 0: en-GB 1: - tag: 32 - value: https://acme.example + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== 2: - - 0 - - 1 - - 2 - -1: 123 Fake Street - 4: - 0: - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + -1: 123 Fake Street + 4: + 0: - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - -1: 1720782190 - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - -1: 1720782190 - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= - -1: 1720782190 + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 diff --git a/corim/testcases/src/example-corim.yaml b/corim/testcases/src/example-corim.yaml index 55d45847..a31b0bad 100644 --- a/corim/testcases/src/example-corim.yaml +++ b/corim/testcases/src/example-corim.yaml @@ -6,73 +6,75 @@ # string, which is differentiated so that the example (which uses init() # to register the profile) doesn't clash with the tests. --- -0: test corim id -3: http://example.com/example-profile --1: foo -1: -- encodedCBOR: - tag: 506 - value: - 0: en-GB - 1: - 0: !!binary |- - Q7vjfy5hSzOu01PP8UKLFg== - 2: - - 0: ACME Ltd. +tag: 501 +value: + 0: test corim id + 3: http://example.com/example-profile + -1: foo + 1: + - encodedCBOR: + tag: 506 + value: + 0: en-GB 1: - tag: 32 - value: https://acme.example + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== 2: - - 0 - - 1 - - 2 - -1: 123 Fake Street - 4: - 0: - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + -1: 123 Fake Street + 4: + 0: - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - -1: 1720782190 - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - -1: 1720782190 - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= - -1: 1720782190 + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 diff --git a/corim/testcases/src/good-corim.yaml b/corim/testcases/src/good-corim.yaml index 4e61fee6..932d5fff 100644 --- a/corim/testcases/src/good-corim.yaml +++ b/corim/testcases/src/good-corim.yaml @@ -1,66 +1,68 @@ # minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate --- -0: test corim id -1: -- encodedCBOR: - tag: 506 - value: - 0: en-GB - 1: - 0: !!binary |- - Q7vjfy5hSzOu01PP8UKLFg== - 2: - - 0: ACME Ltd. +tag: 501 +value: + 0: test corim id + 1: + - encodedCBOR: + tag: 506 + value: + 0: en-GB 1: - tag: 32 - value: https://acme.example + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== 2: - - 0 - - 1 - - 2 - 4: - 0: - - - 0: - 0: - tag: 600 - value: !!binary |- - YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= - 1: ACME - 2: RoadRunner + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + 4: + 0: - - 0: - tag: 601 - value: - 1: BL - 4: 2.1.0 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= - - 0: - tag: 601 - value: - 1: PRoT - 4: 1.3.5 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= - - 0: - tag: 601 - value: - 1: ARoT - 4: 0.1.4 - 5: !!binary |- - rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= - 1: - 2: - - - 1 - - !!binary |- - o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= diff --git a/corim/testcases/unsigned-corim-with-extensions.cbor b/corim/testcases/unsigned-corim-with-extensions.cbor index 0e056df92a7d13cf5caff8df0858e92e82bb1894..37fa07cfb56ca84c9977d21b2ee257314534ac48 100644 GIT binary patch delta 65 zcmZo-X=P!#$@q22L=AO8g?vLJV+FUw>{Nx|lA_eql8OEr94reN8k-g}ES@+=hleRE VC_gbJs5CDxwP@oBE5?b8>Hx$D76kwR delta 61 zcmZo=X<}hqGWi~(`a~A>i8dOl3i*ac#tLqU*{KS_B}J*JB`gaW8k-g}EN03I%1=xQ RD$UDFEt;64vvG+PBLG#C6o>!- diff --git a/corim/testcases/unsigned-example-corim.cbor b/corim/testcases/unsigned-example-corim.cbor index 0c715c8f24d0886906a5f8df3eb40b02d6b4d4dd..58262f524faa101ec3e524433334edb0097d76a7 100644 GIT binary patch delta 188 zcmZo=>0r^m$@p~%V`C)av73y)mN2BI=IOdSF)m~XaNhm6UN6zxc-`gT^Bf)Bsux8q#EfN>KQODn%J(!qac+wkvDG_s1Ex7;y diff --git a/corim/testcases/unsigned-good-corim.cbor b/corim/testcases/unsigned-good-corim.cbor index 3c1fcb6c93b41b5fd26b80922fe99fa8dffd7a4a..d387b7b62f90ea4efce22daf04c2611a80f9c4c2 100644 GIT binary patch delta 109 zcmdnNyqlT%Cgax$<{FGQ6iPBmN(zdt^z{>yb5r$FD-v@Ha#ER^85o%uG98_LT@`#v zQuHRqyK^%pIr*@p8tED88B9)KRGD~Hcj7V?o|J%~{1BE@Lp@_X)5%7Rdh9Hz20$ey MKqZ=!CopON0CY|u9smFU delta 111 zcmdnZyn}fnv$+OSGXo Date: Mon, 16 Jun 2025 11:10:07 +0100 Subject: [PATCH 099/110] fix: handle short buffers inside UnsignedCorim.FromCBOR() UnsignedCorim.FromCBOR() checks for the unsigned corim tag by taking a slice of the first three bytes of the input. This will panic if the input is shorter than three bytes, so check for that first. Signed-off-by: Sergei Trofimov --- corim/unsignedcorim.go | 4 ++++ corim/unsignedcorim_test.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index c51736f3..058c6f99 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -302,6 +302,10 @@ func (o UnsignedCorim) ToCBOR() ([]byte, error) { // FromCBOR deserializes a CBOR-encoded unsigned CoRIM into the target UnsignedCorim func (o *UnsignedCorim) FromCBOR(data []byte) error { + if len(data) < 3 { + return errors.New("input too short") + } + if !bytes.Equal(data[:3], UnsignedCorimTag) { return errors.New("did not see unsigned CoRIM tag") } diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 4af79adb..d4f5e64d 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -404,6 +404,12 @@ func TestUnsignedCorim_extensions(t *testing.T) { assert.EqualError(t, err, `unexpected extension point: "test"`) } +func TestUnsignedCorim_truncated(t *testing.T) { + c := NewUnsignedCorim() + err := c.FromCBOR([]byte{0x01, 0x02}) + assert.EqualError(t, err, "input too short") +} + func TestLocator_Valid(t *testing.T) { l := Locator{} assert.EqualError(t, l.Valid(), "empty href") From 072477faaf9567bbcd5894884565e61654b70e7c Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Tue, 17 Jun 2025 11:10:15 +0200 Subject: [PATCH 100/110] feat(comid): Add `tagged-bytes` to `$crypto-key-type-choice` (#191) Fix #190 Signed-off-by: Thomas Fossati --- comid/cryptokey.go | 25 +++++++++++++++++++++++++ comid/cryptokey_test.go | 28 +++++++++++++++++++++++++++- comid/test_vars.go | 2 ++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/comid/cryptokey.go b/comid/cryptokey.go index 3133b95e..5c4ea1de 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -47,6 +47,8 @@ const ( // The digest value may be used to find the certificate path if // contained in a lookup table. CertPathThumbprintType = "cert-path-thumbprint" + + // Note: the tagged-bytes type name is already defined in bytes.go as BytesType ) // CryptoKey is the struct implementing CoRIM crypto-key-type-choice. See @@ -736,6 +738,7 @@ var cryptoKeyValueRegister = map[string]ICryptoKeyFactory{ ThumbprintType: NewThumbprint, CertThumbprintType: NewCertThumbprint, CertPathThumbprintType: NewCertPathThumbprint, + BytesType: NewCryptoKeyTaggedBytes, } // RegisterCryptoKeyType registers a new ICryptoKeyValue implementation @@ -761,3 +764,25 @@ func RegisterCryptoKeyType(tag uint64, factory ICryptoKeyFactory) error { return nil } + +func (o TaggedBytes) PublicKey() (crypto.PublicKey, error) { + return nil, errors.New("cannot get PublicKey from bytes") +} + +func NewCryptoKeyTaggedBytes(val any) (*CryptoKey, error) { + tb, err := NewBytes(val) + if err != nil { + return nil, err + } + + return &CryptoKey{tb}, nil +} + +func MustNewCryptoKeyTaggedBytes(val any) *CryptoKey { + key, err := NewCryptoKeyTaggedBytes(val) + if err != nil { + panic(err) + } + + return key +} diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index 11f05ade..cf950bbd 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -194,12 +194,25 @@ func Test_CryptoKey_NewThumbprint(t *testing.T) { } } +func Test_CryptoKey_NewTaggedBytes(t *testing.T) { + key, err := NewCryptoKeyTaggedBytes(TestTaggedBytes) + require.NoError(t, err) + assert.Equal(t, string(TestTaggedBytes), key.String()) + _, err = key.PublicKey() + assert.EqualError(t, err, "cannot get PublicKey from bytes") +} + func Test_CryptoKey_JSON_roundtrip(t *testing.T) { for _, tv := range []struct { Type string In any Out string }{ + { + Type: BytesType, + In: TestTaggedBytes, + Out: string(TestTaggedBytes), + }, { Type: PKIXBase64KeyType, In: TestECPubKey, @@ -266,6 +279,10 @@ func Test_CryptoKey_UnmarshalJSON_negative(t *testing.T) { Val string ErrMsg string }{ + { + Val: `{"type": "bytes", "value": 1}`, + ErrMsg: "json: cannot unmarshal", + }, { Val: `@@`, ErrMsg: "invalid character", @@ -298,6 +315,11 @@ func Test_CryptoKey_CBOR_roundtrip(t *testing.T) { In any Out string }{ + { + Type: BytesType, + In: TestTaggedBytes, + Out: "d902304b7461676765646279746573", + }, { Type: PKIXBase64KeyType, In: TestECPubKey, @@ -354,6 +376,11 @@ func Test_NewCryptoKey_negative(t *testing.T) { In any ErrMsg string }{ + { + Type: BytesType, + In: 7, + ErrMsg: "unexpected type for bytes: int", + }, { Type: PKIXBase64KeyType, In: 7, @@ -395,7 +422,6 @@ func Test_NewCryptoKey_negative(t *testing.T) { ErrMsg: "unexpected CryptoKey type: random-key", }, } { - _, err := NewCryptoKey(tv.In, tv.Type) assert.ErrorContains(t, err, tv.ErrMsg) } diff --git a/comid/test_vars.go b/comid/test_vars.go index 49de3ba6..fe426d26 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -186,6 +186,8 @@ rpT6kiSnAzk+2zgSiA4= HashAlgID: 1, HashValue: MustHexDecode(nil, `68e656b251e67e8358bef8483ab0d51c6619f3e7a1a9f0e75838d41ff368f728`), } + + TestTaggedBytes = []byte("taggedbytes") ) func MustHexDecode(t *testing.T, s string) []byte { From 3a0b929cd8b793daea97d450ef782fc24e3c1879 Mon Sep 17 00:00:00 2001 From: Pranjal Kole Date: Thu, 2 Jan 2025 00:57:53 +0530 Subject: [PATCH 101/110] fix some golangci-lint warnings Signed-off-by: Pranjal Kole --- encoding/cbor.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/encoding/cbor.go b/encoding/cbor.go index 74656bd8..afe76feb 100644 --- a/encoding/cbor.go +++ b/encoding/cbor.go @@ -261,13 +261,13 @@ func (o *structFieldsCBOR) ToCBOR(em cbor.EncMode) ([]byte, error) { header |= byte(25) out = append(out, header) out = binary.BigEndian.AppendUint16(out, uint16(mapLen)) - } else { + } else if mapLen <= math.MaxUint32 { header |= byte(26) out = append(out, header) out = binary.BigEndian.AppendUint32(out, uint32(mapLen)) + } else { + return nil, errors.New("mapLen cannot exceed math.MaxUint32") } - // Since len() returns an int, the value cannot exceed MaxUint32, so - // the 8-byte length variant cannot occur. for _, key := range o.Keys { marshalledKey, err := em.Marshal(key) From b6f208c23885130f03accacea44d0940fa67755c Mon Sep 17 00:00:00 2001 From: Dionna Amalie Glaze Date: Wed, 18 Jun 2025 01:38:25 -0700 Subject: [PATCH 102/110] Update golangci-lint version to v2 (#194) * Update versions golangci-lint v2 uses a different syntax and has deprecated some checks. gosimple has been merged into staticcheck. typecheck is not a linter and has been removed. maligned has been deprecated. Signed-off-by: Dionna Glaze * Fix QF1008 lint failures. Signed-off-by: Dionna Glaze * Fix lint error QF1006 Signed-off-by: Dionna Glaze * Allow test functions to be long. Signed-off-by: Dionna Glaze * Disable revive linter Issue #192 Signed-off-by: Dionna Glaze * Allow long lines in tests. Signed-off-by: Dionna Glaze * Allow duplicate constants in tests Concrete values in tests are valuable for clearer auditability to determine correctness. Signed-off-by: Dionna Glaze * Allow duplicate code in tests Abstracting test logic into methods is generally not the best practice in order to keep tests self-contained and locally auditable for correctness. Signed-off-by: Dionna Glaze * Fix gocritic linter error. Signed-off-by: Dionna Glaze * Account for integer conversion errors. Resolves gosec errors. Signed-off-by: Dionna Glaze * Move test constants to const block When `const`, values are typeless, which allows for safe interpretation into different types. This resolves an otherwise erroneous int conversion safety error from gosec. Signed-off-by: Dionna Glaze * Use go:embed for test data files. The gosec linter does not approve of os.ReadFile in tests. It's better to make the test data into constants directly usable by tests using go:embed. Signed-off-by: Dionna Glaze --------- Signed-off-by: Dionna Glaze --- .github/workflows/linters.yml | 4 +- .golangci.yml | 115 +++++++++--------- Makefile | 2 +- comid/classid.go | 41 ++++++- comid/comid.go | 6 +- comid/cryptokey.go | 6 +- comid/entity.go | 6 +- comid/flagsmap.go | 18 +-- comid/measurement.go | 10 +- comid/svn_test.go | 4 +- comid/tdx-profile/example_pce_refval_test.go | 12 +- comid/tdx-profile/example_qe_refval_test.go | 18 +-- comid/tdx-profile/example_seam_refval_test.go | 26 ++-- comid/tdx-profile/teeinstanceid_test.go | 2 +- comid/tdx-profile/test_vars.go | 45 +++---- comid/triples.go | 6 +- corim/entity.go | 6 +- corim/example_profile_test.go | 7 +- corim/extensions_test.go | 4 +- corim/meta.go | 2 +- corim/meta_test.go | 2 +- corim/profiles_test.go | 24 ++-- corim/signedcorim_test.go | 64 +++------- corim/signer.go | 6 +- corim/signer_test.go | 4 +- corim/unsignedcorim.go | 6 +- extensions/collection_test.go | 12 +- go.mod | 7 +- go.sum | 6 +- scripts/gen-certs.sh | 4 +- {misc => testdata}/endEntity.der | Bin {misc => testdata}/endEntity.key | 0 {misc => testdata}/intermediateCA.der | Bin {misc => testdata}/rootCA.der | Bin testdata/testdata.go | 25 ++++ 35 files changed, 264 insertions(+), 236 deletions(-) rename {misc => testdata}/endEntity.der (100%) rename {misc => testdata}/endEntity.key (100%) rename {misc => testdata}/intermediateCA.der (100%) rename {misc => testdata}/rootCA.der (100%) create mode 100644 testdata/testdata.go diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index c61c4f87..cbd8831c 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -10,13 +10,13 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: "1.22" + go-version: "1.23" - name: Checkout code uses: actions/checkout@v2 - name: Install golangci-lint run: | go version - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6 - name: Install mockgen run: | go install github.com/golang/mock/mockgen@v1.5.0 diff --git a/.golangci.yml b/.golangci.yml index 51cc9e70..aecdb493 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,60 +1,68 @@ # Do not delete linter settings. Linters like gocritic can be enabled on the command line. -linters-settings: - dupl: - threshold: 100 - funlen: - lines: 100 - statements: 50 - goconst: - min-len: 2 - min-occurrences: 3 - gocritic: - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style - disabled-checks: - - dupImport # https://github.com/go-critic/go-critic/issues/845 - - ifElseChain - - octalLiteral - - paramTypeCombine - - whyNoLint - - wrapperFunc - gofmt: - simplify: false - goimports: - golint: - min-confidence: 0 - govet: - check-shadowing: true - lll: - line-length: 140 - maligned: - suggest-new: true - misspell: - locale: US - -linters: - disable-all: true +formatters: enable: - - errcheck - - goconst - - gocyclo - gofmt - goimports - - gosec - - govet - - ineffassign - - misspell - - revive - - staticcheck - - typecheck - - unconvert - - unused + settings: + gofmt: + simplify: false +linters: + default: none + enable: + - dupl # style + - errcheck # bugs + - funlen # complexity + - goconst # style + - gocritic # metalinter + - gocyclo # complexity + - gosec # bugs + - govet # bugs + - ineffassign + - lll # style + - misspell # comment + - staticcheck # metalinter + - unconvert # style + - unused # unused + exclusions: + rules: + - path: '(.+)_test\.go' + linters: + - dupl + - funlen + - lll + - goconst + settings: + dupl: + threshold: 100 + funlen: + lines: 100 + statements: 50 + goconst: + min-len: 2 + min-occurrences: 3 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - ifElseChain + - octalLiteral + - paramTypeCombine + - whyNoLint + - wrapperFunc + govet: + enable: + - shadow + lll: + line-length: 140 + misspell: + locale: US issues: # max-issues-per-linter default is 50. Set to 0 to disable limit. @@ -80,7 +88,4 @@ issues: # see it as unused - unused -# golangci.com configuration -# https://github.com/golangci/golangci/wiki/Configuration -service: - golangci-lint-version: 1.23.x # use the fixed version to not introduce new linters unexpectedly +version: "2" diff --git a/Makefile b/Makefile index 4e52f6f2..72f7fcdd 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ GOPKG += github.com/veraison/corim/extensions GOLINT ?= golangci-lint -GOLINT_ARGS ?= run --timeout=3m -E dupl -E gocritic -E gosimple -E lll -E prealloc +GOLINT_ARGS ?= run --timeout=3m -E dupl -E gocritic -E staticcheck -E lll -E prealloc .PHONY: lint lint: diff --git a/comid/classid.go b/comid/classid.go index 05307f12..4ab881a9 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -11,6 +11,7 @@ import ( "fmt" "strconv" + "fortio.org/safecast" "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" ) @@ -377,19 +378,39 @@ func NewIntClassID(val any) (*ClassID, error) { if len(t) != 8 { return nil, fmt.Errorf("bad int: want 8 bytes, got %d bytes", len(t)) } - ret = TaggedInt(binary.BigEndian.Uint64(t)) + ti, err := safecast.Convert[int, uint64](binary.BigEndian.Uint64(t)) + if err != nil { + return nil, err + } + ret = TaggedInt(ti) case int: ret = TaggedInt(t) case *int: ret = TaggedInt(*t) case int64: - ret = TaggedInt(t) + ti, err := safecast.Convert[int, int64](t) + if err != nil { + return nil, err + } + ret = TaggedInt(ti) case *int64: - ret = TaggedInt(*t) + ti, err := safecast.Convert[int, int64](*t) + if err != nil { + return nil, err + } + ret = TaggedInt(ti) case uint64: - ret = TaggedInt(t) + ti, err := safecast.Convert[int, uint64](t) + if err != nil { + return nil, err + } + ret = TaggedInt(ti) case *uint64: - ret = TaggedInt(*t) + ti, err := safecast.Convert[int, uint64](*t) + if err != nil { + return nil, err + } + ret = TaggedInt(ti) default: return nil, fmt.Errorf("unexpected type for int: %T", t) } @@ -415,7 +436,15 @@ func (o TaggedInt) Type() string { func (o TaggedInt) Bytes() []byte { var ret [8]byte - binary.BigEndian.PutUint64(ret[:], uint64(o)) + var uo uint64 + io := int(o) // Needed for gosec flow typing. + // Use 2's complement for negative values since this can't return an error. + if io < 0 { + uo = ^uint64(0) - uint64(-io) + 1 + } else { + uo = uint64(io) + } + binary.BigEndian.PutUint64(ret[:], uo) return ret[:] } diff --git a/comid/comid.go b/comid/comid.go index 34fade36..89ee3693 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -37,7 +37,7 @@ func (o *Comid) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtComid: - o.Extensions.Register(v) + o.Register(v) case ExtEntity: if o.Entities == nil { o.Entities = NewEntities() @@ -57,7 +57,7 @@ func (o *Comid) RegisterExtensions(exts extensions.Map) error { // GetExtensions returns previously registered extension func (o *Comid) GetExtensions() extensions.IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } // SetLanguage sets the language used in the target Comid to the supplied @@ -279,7 +279,7 @@ func (o Comid) Valid() error { return fmt.Errorf("triples validation failed: %w", err) } - return o.Extensions.validComid(&o) + return o.validComid(&o) } // ToCBOR serializes the target Comid to CBOR diff --git a/comid/cryptokey.go b/comid/cryptokey.go index 5c4ea1de..4d2a4d1b 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -393,11 +393,7 @@ func (o TaggedPKIXBase64CertPath) certPath() ([]*x509.Certificate, error) { var rest []byte rest = []byte(o) i := 0 - for { - if len(rest) == 0 { - break - } - + for len(rest) != 0 { block, rest = pem.Decode(rest) if block == nil { return nil, fmt.Errorf("could not decode PEM block %d", i) diff --git a/comid/entity.go b/comid/entity.go index 72db4929..170f706a 100644 --- a/comid/entity.go +++ b/comid/entity.go @@ -27,7 +27,7 @@ func (o *Entity) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtEntity: - o.Extensions.Register(v) + o.Register(v) default: return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) } @@ -38,7 +38,7 @@ func (o *Entity) RegisterExtensions(exts extensions.Map) error { // GetExtensions returns previously registered extension func (o *Entity) GetExtensions() extensions.IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } // SetName is used to set the Name field of Entity using supplied name @@ -90,7 +90,7 @@ func (o Entity) Valid() error { return fmt.Errorf("invalid entity: %w", err) } - return o.Extensions.validEntity(&o) + return o.validEntity(&o) } // UnmarshalCBOR deserializes from CBOR diff --git a/comid/flagsmap.go b/comid/flagsmap.go index bdcf298c..58d304d4 100644 --- a/comid/flagsmap.go +++ b/comid/flagsmap.go @@ -76,7 +76,7 @@ func (o FlagsMap) IsEmpty() bool { return false } - return o.Extensions.IsEmpty() + return o.IsEmpty() } func (o *FlagsMap) AnySet() bool { @@ -86,7 +86,7 @@ func (o *FlagsMap) AnySet() bool { return true } - return o.Extensions.anySet() + return o.anySet() } func (o *FlagsMap) setFlag(value *bool, flags ...Flag) { @@ -112,9 +112,9 @@ func (o *FlagsMap) setFlag(value *bool, flags ...Flag) { o.IsTcb = value default: if value == &True { - o.Extensions.setTrue(flag) + o.setTrue(flag) } else { - o.Extensions.setFalse(flag) + o.setFalse(flag) } } } @@ -150,7 +150,7 @@ func (o *FlagsMap) Clear(flags ...Flag) { case FlagIsTcb: o.IsTcb = nil default: - o.Extensions.clear(flag) + o.clear(flag) } } } @@ -176,7 +176,7 @@ func (o *FlagsMap) Get(flag Flag) *bool { case FlagIsTcb: return o.IsTcb default: - return o.Extensions.get(flag) + return o.get(flag) } } @@ -193,7 +193,7 @@ func (o *FlagsMap) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtFlags: - o.Extensions.Register(v) + o.Register(v) default: return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) } @@ -204,7 +204,7 @@ func (o *FlagsMap) RegisterExtensions(exts extensions.Map) error { // GetExtensions returns previously registered extension func (o *FlagsMap) GetExtensions() extensions.IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } // UnmarshalCBOR deserializes from CBOR @@ -232,5 +232,5 @@ func (o FlagsMap) MarshalJSON() ([]byte, error) { // Valid returns an error if the FlagsMap is invalid. // nolint:gocritic func (o FlagsMap) Valid() error { - return o.Extensions.validFlagsMap(&o) + return o.validFlagsMap(&o) } diff --git a/comid/measurement.go b/comid/measurement.go index 9ff21627..ff105e09 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -363,13 +363,13 @@ func (o *Mval) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtMval: - o.Extensions.Register(v) + o.Register(v) case ExtFlags: if o.Flags == nil { o.Flags = new(FlagsMap) } - o.Flags.Extensions.Register(v) + o.Flags.Register(v) default: return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) } @@ -380,7 +380,7 @@ func (o *Mval) RegisterExtensions(exts extensions.Map) error { // GetExtensions returns pervisouosly registered extension func (o *Mval) GetExtensions() extensions.IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } // UnmarshalCBOR deserializes from CBOR @@ -442,7 +442,7 @@ func (o Mval) Valid() error { o.UUID == nil && o.Name == nil && o.IntegrityRegisters == nil && - o.Extensions.IsEmpty() { + o.IsEmpty() { return fmt.Errorf("no measurement value set") } @@ -488,7 +488,7 @@ func (o Mval) Valid() error { // raw value and raw-value-mask have no specific semantics here // Validate extensions (custom logic implemented in validMval()) - return o.Extensions.validMval(&o) + return o.validMval(&o) } // Measurement stores a measurement-map with CBOR and JSON serializations. diff --git a/comid/svn_test.go b/comid/svn_test.go index b8876720..724627cf 100644 --- a/comid/svn_test.go +++ b/comid/svn_test.go @@ -83,8 +83,8 @@ func Test_NewSVN(t *testing.T) { } retMin, err := NewSVN(tv.Input, "min-value") - min := TaggedMinSVN(tv.Expected) - expected = SVN{&min} + svnMin := TaggedMinSVN(tv.Expected) + expected = SVN{&svnMin} if tv.Err != "" { assert.EqualError(t, err, tv.Err) diff --git a/comid/tdx-profile/example_pce_refval_test.go b/comid/tdx-profile/example_pce_refval_test.go index 5d52c6e0..168f10b3 100644 --- a/comid/tdx-profile/example_pce_refval_test.go +++ b/comid/tdx-profile/example_pce_refval_test.go @@ -132,7 +132,7 @@ func extractPCEMeasurements(meas *comid.Measurements) error { } func decodePCEMValExtensions(m *comid.Measurement) error { - val, err := m.Val.Extensions.Get("instanceid") + val, err := m.Val.Get("instanceid") if err != nil { return errors.New("failed to decode instanceid from measurement extensions") } @@ -157,7 +157,7 @@ func decodePCEMValExtensions(m *comid.Measurement) error { return errors.New("teeinstanceid is neither integer or byte string") } - val, err = m.Val.Extensions.Get("tcbcompsvn") + val, err = m.Val.Get("tcbcompsvn") if err != nil { return errors.New("failed to decode teetcbcompsvn from measurement extensions") } @@ -170,7 +170,7 @@ func decodePCEMValExtensions(m *comid.Measurement) error { return fmt.Errorf("invalid computed SVN: %w", err) } - val, err = m.Val.Extensions.Get("pceid") + val, err = m.Val.Get("pceid") if err != nil { return errors.New("failed to decode tcbevalnum from measurement extensions") } @@ -347,7 +347,7 @@ func setTDXPCEMvalExtension(val *comid.Mval) error { if err != nil { return fmt.Errorf("unable to get teeinstanceID: %w", err) } - err = val.Extensions.Set("instanceid", instanceID) + err = val.Set("instanceid", instanceID) if err != nil { return fmt.Errorf("unable to set teeinstanceID: %w", err) } @@ -356,7 +356,7 @@ func setTDXPCEMvalExtension(val *comid.Mval) error { if err != nil { return fmt.Errorf("unable to get NewTeepceID: %w", err) } - err = val.Extensions.Set("pceid", p) + err = val.Set("pceid", p) if err != nil { return fmt.Errorf("unable to set teepceID: %w", err) } @@ -366,7 +366,7 @@ func setTDXPCEMvalExtension(val *comid.Mval) error { return fmt.Errorf("failed to get TeeTcbCompSvn: %w", err) } - err = val.Extensions.Set("tcbcompsvn", c) + err = val.Set("tcbcompsvn", c) if err != nil { return fmt.Errorf("unable to set teetcbcompsvn: %w", err) } diff --git a/comid/tdx-profile/example_qe_refval_test.go b/comid/tdx-profile/example_qe_refval_test.go index fd0c6c90..37fe8c39 100644 --- a/comid/tdx-profile/example_qe_refval_test.go +++ b/comid/tdx-profile/example_qe_refval_test.go @@ -109,7 +109,7 @@ func extractQEMeasurements(meas *comid.Measurements) error { } func decodeQEMValExtensions(m *comid.Measurement) error { - val, err := m.Val.Extensions.Get("miscselect") + val, err := m.Val.Get("miscselect") if err != nil { return errors.New("failed to decode miscselect from measurement extensions") } @@ -120,7 +120,7 @@ func decodeQEMValExtensions(m *comid.Measurement) error { miscselect := *f fmt.Printf("\nmiscselect: %x", miscselect) - val, err = m.Val.Extensions.Get("tcbevalnum") + val, err = m.Val.Get("tcbevalnum") if err != nil { return errors.New("failed to decode tcbevalnum from measurement extensions") } @@ -133,7 +133,7 @@ func decodeQEMValExtensions(m *comid.Measurement) error { return fmt.Errorf("failed to extract tcbevalnum: %w", err) } - val, err = m.Val.Extensions.Get("isvprodid") + val, err = m.Val.Get("isvprodid") if err != nil { return errors.New("failed to decode isvprodid from measurement extensions") } @@ -146,7 +146,7 @@ func decodeQEMValExtensions(m *comid.Measurement) error { return fmt.Errorf("failed to decode teeISVProdID from measurement extensions: %w", err) } - val, err = m.Val.Extensions.Get("mrsigner") + val, err = m.Val.Get("mrsigner") if err != nil { return errors.New("failed to decode mrsigner from measurement extensions") } @@ -228,19 +228,19 @@ func setTDXQEMvalExtensions(val *comid.Mval) error { return fmt.Errorf("unable to get isvprodid: %w", err) } - err = val.Extensions.Set("isvprodid", isvProdID) + err = val.Set("isvprodid", isvProdID) if err != nil { return fmt.Errorf("unable to set isvprodid: %w", err) } - err = val.Extensions.Set("isvsvn", svn) + err = val.Set("isvsvn", svn) if err != nil { return fmt.Errorf("unable to set isvsvn: %w", err) } - err = val.Extensions.Set("tcbevalnum", teeTcbEvNum) + err = val.Set("tcbevalnum", teeTcbEvNum) if err != nil { return fmt.Errorf("unable to set tcbevalnum: %w", err) } - err = val.Extensions.Set("miscselect", &teeMiscSel) + err = val.Set("miscselect", &teeMiscSel) if err != nil { return fmt.Errorf("unable to set miscselect: %w", err) } @@ -254,7 +254,7 @@ func setTDXQEMvalExtensions(val *comid.Mval) error { return fmt.Errorf("unable to get TeeDigest: %w", err) } - err = val.Extensions.Set("mrsigner", td) + err = val.Set("mrsigner", td) if err != nil { return fmt.Errorf("unable to set mrsigner: %w", err) } diff --git a/comid/tdx-profile/example_seam_refval_test.go b/comid/tdx-profile/example_seam_refval_test.go index c8aa5319..4365bf07 100644 --- a/comid/tdx-profile/example_seam_refval_test.go +++ b/comid/tdx-profile/example_seam_refval_test.go @@ -222,7 +222,7 @@ func Example_encode_tdx_seam_refval_direct() { func setTDXSeamMvalExtensions(val *comid.Mval) error { tcbDate, _ := time.Parse(time.RFC3339, "2025-01-27T00:00:00Z") - err := val.Extensions.Set("tcbdate", &tcbDate) + err := val.Set("tcbdate", &tcbDate) if err != nil { return fmt.Errorf("unable to set tcbDate: %w", err) } @@ -231,7 +231,7 @@ func setTDXSeamMvalExtensions(val *comid.Mval) error { if err != nil { return fmt.Errorf("unable to get isvprodid: %w", err) } - err = val.Extensions.Set("isvprodid", isvProdID) + err = val.Set("isvprodid", isvProdID) if err != nil { return fmt.Errorf("unable to set isvprodid: %w", err) } @@ -239,7 +239,7 @@ func setTDXSeamMvalExtensions(val *comid.Mval) error { if err != nil { return fmt.Errorf("unable to get isvsvn numeric: %w", err) } - err = val.Extensions.Set("isvsvn", svn) + err = val.Set("isvsvn", svn) if err != nil { return fmt.Errorf("unable to set isvsvn: %w", err) } @@ -247,7 +247,7 @@ func setTDXSeamMvalExtensions(val *comid.Mval) error { if err != nil { return fmt.Errorf("unable to get tcbevalnum numeric: %w", err) } - err = val.Extensions.Set("tcbevalnum", teeTcbEvNum) + err = val.Set("tcbevalnum", teeTcbEvNum) if err != nil { return fmt.Errorf("unable to set tcbevalnum: %w", err) } @@ -256,7 +256,7 @@ func setTDXSeamMvalExtensions(val *comid.Mval) error { if err != nil { return fmt.Errorf("unable to get teeAttributes: %w", err) } - err = val.Extensions.Set("attributes", teeAttr) + err = val.Set("attributes", teeAttr) if err != nil { return fmt.Errorf("unable to set attributes: %w", err) } @@ -268,7 +268,7 @@ func setTDXSeamMvalExtensions(val *comid.Mval) error { return fmt.Errorf("unable to get TeeDigest: %w", err) } - err = val.Extensions.Set("mrtee", td) + err = val.Set("mrtee", td) if err != nil { return fmt.Errorf("unable to set mrtee: %w", err) } @@ -282,7 +282,7 @@ func setTDXSeamMvalExtensions(val *comid.Mval) error { return fmt.Errorf("unable to get TeeDigest: %w", err) } - err = val.Extensions.Set("mrsigner", td) + err = val.Set("mrsigner", td) if err != nil { return fmt.Errorf("unable to set mrsigner %w", err) } @@ -290,7 +290,7 @@ func setTDXSeamMvalExtensions(val *comid.Mval) error { } func decodeMValExtensions(m *comid.Measurement) error { - val, err := m.Val.Extensions.Get("tcbevalnum") + val, err := m.Val.Get("tcbevalnum") if err != nil { return fmt.Errorf("failed to decode tcbevalnum from measurement extensions: %w", err) } @@ -303,7 +303,7 @@ func decodeMValExtensions(m *comid.Measurement) error { return fmt.Errorf("failed to extract tcbevalnum: %w", err) } - val, err = m.Val.Extensions.Get("isvprodid") + val, err = m.Val.Get("isvprodid") if err != nil { return errors.New("failed to decode isvprodid from measurement extensions") } @@ -315,7 +315,7 @@ func decodeMValExtensions(m *comid.Measurement) error { return fmt.Errorf("failed to decode teeISVProdID from measurement extensions: %w", err) } - val, err = m.Val.Extensions.Get("isvsvn") + val, err = m.Val.Get("isvsvn") if err != nil { return errors.New("failed to decode isvsvn from measurement extensions") } @@ -332,7 +332,7 @@ func decodeMValExtensions(m *comid.Measurement) error { if err != nil { return fmt.Errorf("unable to extract tee svn: %w", err) } - val, err = m.Val.Extensions.Get("attributes") + val, err = m.Val.Get("attributes") if err != nil { return errors.New("failed to decode attributes from measurement extensions") } @@ -343,7 +343,7 @@ func decodeMValExtensions(m *comid.Measurement) error { } fmt.Printf("\nAttributes: %x", *tA) - val, err = m.Val.Extensions.Get("mrtee") + val, err = m.Val.Get("mrtee") if err != nil { return errors.New("failed to decode mrtee from measurement extensions") } @@ -357,7 +357,7 @@ func decodeMValExtensions(m *comid.Measurement) error { return fmt.Errorf("failed to decode mrtee from digest: %w", err) } - val, err = m.Val.Extensions.Get("mrsigner") + val, err = m.Val.Get("mrsigner") if err != nil { return fmt.Errorf("failed to decode mrsigner from measurement extensions: %w", err) } diff --git a/comid/tdx-profile/teeinstanceid_test.go b/comid/tdx-profile/teeinstanceid_test.go index 8e976080..78ff2b69 100644 --- a/comid/tdx-profile/teeinstanceid_test.go +++ b/comid/tdx-profile/teeinstanceid_test.go @@ -15,7 +15,7 @@ import ( func TestTeeInstanceID_NewTeeInstanceID_OK(t *testing.T) { tvs := []struct { desc string - input interface{} + input any }{ { desc: "integer", diff --git a/comid/tdx-profile/test_vars.go b/comid/tdx-profile/test_vars.go index 99294ecd..601a7bd8 100644 --- a/comid/tdx-profile/test_vars.go +++ b/comid/tdx-profile/test_vars.go @@ -4,27 +4,17 @@ package tdx //nolint:lll -var ( - TestRegID = "https://intel.com" - TestOID = "2.16.840.1.113741.1.2.3.4.5" - TestUIntInstance = 45 - TestByteInstance = []byte{0x45, 0x46, 0x47} - TestInvalidProdID = -23 - TestUIntISVProdID = 23 - TestBytesISVProdID = []byte{0x01, 0x02, 0x03} - TestInvalidInstance = -1 - TestTeeAttributes = []byte{0x01, 0x01} - TestTeeMiscSelect = []byte{0x0B, 0x0C, 0x0D} - TestPCEID = "PCEID001" - TestCompSvn = []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - TestTCBStatus = []string{"OutOfDate", "ConfigurationNeeded", "UpToDate"} - TestInvalidTCBStatus = []int{1, 2, 3} - TestAdvisoryIDs = []string{"SA-00078", "SA-00077", "SA-00079"} - TestInvalidAdvisoryIDs = []float64{1.234, 2.567} - TestISVSVN = uint(10) - TestTCBEvalNum = uint(11) - TestTime = "2025-01-29T00:00:00Z" - TDXPCERefValTemplate = `{ +const ( + TestUIntInstance = 45 + TestInvalidProdID = -23 + TestUIntISVProdID = 23 + TestInvalidInstance = -1 + TestPCEID = "PCEID001" + TestISVSVN = uint(10) + TestTCBEvalNum = uint(11) + TestTime = "2025-01-29T00:00:00Z" + TestOID = "2.16.840.1.113741.1.2.3.4.5" + TDXPCERefValTemplate = `{ "lang": "en-GB", "tag-identity": { "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B17", @@ -396,3 +386,16 @@ var ( } ` ) + +var ( + TestByteInstance = []byte{0x45, 0x46, 0x47} + TestBytesISVProdID = []byte{0x01, 0x02, 0x03} + TestTeeAttributes = []byte{0x01, 0x01} + TestTeeMiscSelect = []byte{0x0B, 0x0C, 0x0D} + TestCompSvn = []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + TestRegID = "https://intel.com" + TestTCBStatus = []string{"OutOfDate", "ConfigurationNeeded", "UpToDate"} + TestInvalidTCBStatus = []int{1, 2, 3} + TestAdvisoryIDs = []string{"SA-00078", "SA-00077", "SA-00079"} + TestInvalidAdvisoryIDs = []float64{1.234, 2.567} +) diff --git a/comid/triples.go b/comid/triples.go index 9149dc66..259eecde 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -28,7 +28,7 @@ func (o *Triples) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtTriples: - o.Extensions.Register(v) + o.Register(v) case ExtReferenceValue: refValExts[ExtMval] = v case ExtReferenceValueFlags: @@ -81,7 +81,7 @@ func (o *Triples) RegisterExtensions(exts extensions.Map) error { // GetExtensions returns previously registered extension func (o *Triples) GetExtensions() extensions.IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } // UnmarshalCBOR deserializes from CBOR @@ -185,7 +185,7 @@ func (o Triples) Valid() error { } } - return o.Extensions.validTriples(&o) + return o.validTriples(&o) } func (o *Triples) AddReferenceValue(val *ValueTriple) *Triples { diff --git a/corim/entity.go b/corim/entity.go index 00e6daac..c9caaa08 100644 --- a/corim/entity.go +++ b/corim/entity.go @@ -32,7 +32,7 @@ func (o *Entity) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtEntity: - o.Extensions.Register(v) + o.Register(v) default: return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) } @@ -43,7 +43,7 @@ func (o *Entity) RegisterExtensions(exts extensions.Map) error { // GetExtensions returns pervisouosly registered extension func (o *Entity) GetExtensions() extensions.IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } // SetName is used to set the EntityName field of Entity using supplied name @@ -103,7 +103,7 @@ func (o Entity) Valid() error { return fmt.Errorf("invalid entity: %w", err) } - return o.Extensions.validEntity(&o) + return o.validEntity(&o) } // UnmarshalCBOR deserializes from CBOR diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index a0dcda87..b4484443 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -100,15 +100,14 @@ func Example_profile_unmarshal() { fmt.Printf("Language: %s\n", *extractedComid.Language) fmt.Printf("Entity: %s\n", *extractedComid.Entities.Values[0].Name) - fmt.Printf(" %s\n", extractedComid.Entities.Values[0]. - Extensions.MustGetString("Address")) + fmt.Printf(" %s\n", extractedComid.Entities.Values[0].MustGetString("Address")) fmt.Printf("Measurements:\n") for i := range extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values { m := &extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values[i] val := hex.EncodeToString((*m.Val.Digests)[0].HashValue) - tsInt := m.Val.Extensions.MustGetInt64("timestamp") + tsInt := m.Val.MustGetInt64("timestamp") ts := time.Unix(tsInt, 0).UTC() fmt.Printf(" %v taken at %s\n", val, ts.Format("2006-01-02T15:04:05")) @@ -148,7 +147,7 @@ func Example_profile_marshal() { AddEntity("ACME Ltd.", &comid.TestRegID, comid.RoleCreator) address := "123 Fake Street" - err = myComid.Entities.Values[0].Extensions.Set("Address", &address) + err = myComid.Entities.Values[0].Set("Address", &address) if err != nil { log.Fatalf("could not set entity Address: %v", err) } diff --git a/corim/extensions_test.go b/corim/extensions_test.go index d0a57399..fc0222fa 100644 --- a/corim/extensions_test.go +++ b/corim/extensions_test.go @@ -52,8 +52,8 @@ func TestEntityExtensions_Valid(t *testing.T) { err = ent.Valid() assert.NoError(t, err) - assert.EqualError(t, ent.Extensions.validCorim(nil), "invalid") - assert.EqualError(t, ent.Extensions.validSigner(nil), "invalid") + assert.EqualError(t, ent.validCorim(nil), "invalid") + assert.EqualError(t, ent.validSigner(nil), "invalid") } func TestEntityExtensions_CBOR(t *testing.T) { diff --git a/corim/meta.go b/corim/meta.go index d72fe0d4..99c8d13a 100644 --- a/corim/meta.go +++ b/corim/meta.go @@ -28,7 +28,7 @@ func (o *Meta) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtSigner: - o.Signer.Extensions.Register(v) + o.Signer.Register(v) default: return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) } diff --git a/corim/meta_test.go b/corim/meta_test.go index 11a55b0f..a93e913a 100644 --- a/corim/meta_test.go +++ b/corim/meta_test.go @@ -193,7 +193,7 @@ func Test_Meata_extensions(t *testing.T) { err := meta.RegisterExtensions(extMap) assert.NoError(t, err) - assert.True(t, meta.Signer.Extensions.HaveExtensions()) + assert.True(t, meta.Signer.HaveExtensions()) assert.Equal(t, exts, meta.Signer.GetExtensions()) badMap := extensions.NewMap().Add(extensions.Point("test"), exts) diff --git a/corim/profiles_test.go b/corim/profiles_test.go index 731cc980..02452bd7 100644 --- a/corim/profiles_test.go +++ b/corim/profiles_test.go @@ -74,14 +74,14 @@ func TestProfileManifest_getters(t *testing.T) { } c := profileManifest.GetComid() - assert.NotNil(t, c.Extensions.IMapValue) + assert.NotNil(t, c.IMapValue) u := profileManifest.GetUnsignedCorim() - assert.NotNil(t, u.Extensions.IMapValue) + assert.NotNil(t, u.IMapValue) s := profileManifest.GetSignedCorim() - assert.NotNil(t, s.UnsignedCorim.Extensions.IMapValue) - assert.NotNil(t, s.Meta.Signer.Extensions.IMapValue) + assert.NotNil(t, s.UnsignedCorim.IMapValue) + assert.NotNil(t, s.Meta.Signer.IMapValue) } func TestProfileManifest_marshaling(t *testing.T) { @@ -115,7 +115,7 @@ func TestProfileManifest_marshaling(t *testing.T) { assert.NoError(t, err) assert.Equal(t, profID, c.Profile) - assert.Equal(t, "foo", c.Extensions.MustGetString("Extension1")) + assert.Equal(t, "foo", c.MustGetString("Extension1")) profileManifest, ok := GetProfileManifest(c.Profile) assert.True(t, ok) @@ -123,11 +123,11 @@ func TestProfileManifest_marshaling(t *testing.T) { cmd, err := UnmarshalComidFromCBOR(c.Tags[0], c.Profile) assert.NoError(t, err) - address := cmd.Entities.Values[0].Extensions.MustGetString("Address") + address := cmd.Entities.Values[0].MustGetString("Address") assert.Equal(t, "123 Fake Street", address) ts := cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. - Val.Extensions.MustGetInt("timestamp") + Val.MustGetInt("timestamp") assert.Equal(t, 1720782190, ts) unregProfID, err := eat.NewProfile("http://example.com") @@ -136,7 +136,7 @@ func TestProfileManifest_marshaling(t *testing.T) { cmdNoExt, err := UnmarshalComidFromCBOR(c.Tags[0], unregProfID) assert.NoError(t, err) - address = cmdNoExt.Entities.Values[0].Extensions.MustGetString("Address") + address = cmdNoExt.Entities.Values[0].MustGetString("Address") assert.Equal(t, "", address) out, err := c.ToCBOR() @@ -156,7 +156,7 @@ func TestProfileManifest_marshaling(t *testing.T) { assert.NoError(t, err) assert.Equal(t, profID, c.Profile) - assert.Equal(t, "foo", c.Extensions.MustGetString("Extension1")) + assert.Equal(t, "foo", c.MustGetString("Extension1")) cmd = profileManifest.GetComid() err = cmd.FromJSON(testComidJSON) @@ -166,11 +166,11 @@ func TestProfileManifest_marshaling(t *testing.T) { err = cmd.FromJSON(testComidWithExtensionsJSON) assert.NoError(t, err) - address = cmd.Entities.Values[0].Extensions.MustGetString("Address") + address = cmd.Entities.Values[0].MustGetString("Address") assert.Equal(t, "123 Fake Street", address) ts = cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. - Val.Extensions.MustGetInt("timestamp") + Val.MustGetInt("timestamp") assert.Equal(t, 1720782190, ts) s, err := UnmarshalSignedCorimFromCBOR(testGoodSignedCorimCBOR) @@ -181,7 +181,7 @@ func TestProfileManifest_marshaling(t *testing.T) { assert.NoError(t, err) assert.Equal(t, profID, s.UnsignedCorim.Profile) - assert.Equal(t, "foo", s.UnsignedCorim.Extensions.MustGetString("Extension1")) + assert.Equal(t, "foo", s.UnsignedCorim.MustGetString("Extension1")) UnregisterProfile(profID) } diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index cd82a7ac..cea6c17a 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -4,16 +4,14 @@ package corim import ( - "bytes" "fmt" - "os" - "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/veraison/corim/extensions" + "github.com/veraison/corim/testdata" ) var ( @@ -112,6 +110,13 @@ var ( }`) ) +func certChain() []byte { + result := make([]byte, len(testdata.IntermediateCA)+len(testdata.RootCA)) + copy(result, testdata.IntermediateCA) + copy(result[len(testdata.IntermediateCA):], testdata.RootCA) + return result +} + func TestSignedCorim_TaggedFromCOSE_ok(t *testing.T) { /* 500( @@ -467,10 +472,6 @@ func TestSignedCorim_extensions(t *testing.T) { } func TestSignedCorim_AddSigningCert(t *testing.T) { - certPath := filepath.Join("..", "misc", "endEntity.der") - validCert, err := os.ReadFile(certPath) - require.NoError(t, err, "Failed to read test certificate.") - tests := []struct { name string certDer []byte @@ -478,7 +479,7 @@ func TestSignedCorim_AddSigningCert(t *testing.T) { errMsg string }{ // Positive test - valid certificate - {"valid cert", validCert, false, ""}, + {"valid cert", testdata.EndEntityDer, false, ""}, // Negative test - nil input {"nil cert", nil, true, "nil signing cert"}, // Negative test - invalid certificate data @@ -502,30 +503,9 @@ func TestSignedCorim_AddSigningCert(t *testing.T) { } } -func concatFiles(files ...string) ([]byte, error) { - var buf bytes.Buffer - - for _, file := range files { - b, err := os.ReadFile(file) - if err != nil { - return nil, err - } - buf.Write(b) - } - - return buf.Bytes(), nil -} - func TestSignedCorim_AddIntermediateCerts(t *testing.T) { - certPath := filepath.Join("..", "misc", "intermediateCA.der") - validCert, err := os.ReadFile(certPath) - require.NoError(t, err, "Failed to read test certificate") - // Add concatenated certificates for testing certificate chains - validCertChain, err := concatFiles( - filepath.Join("..", "misc", "intermediateCA.der"), - filepath.Join("..", "misc", "rootCA.der")) - require.NoError(t, err, "Failed to read certificate chain") + validCertChain := certChain() tests := []struct { name string @@ -535,7 +515,7 @@ func TestSignedCorim_AddIntermediateCerts(t *testing.T) { certCount int }{ // Positive test - valid certificate - {"valid cert", validCert, false, "", 1}, + {"valid cert", testdata.IntermediateCA, false, "", 1}, // Positive test - certificate chain {"cert chain", validCertChain, false, "", 2}, // Negative test - empty input @@ -575,19 +555,12 @@ func TestSignedCorim_SignVerify_with_x5chain_ok(t *testing.T) { signedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) signedCorimIn.Meta = *metaGood(t) - endEntityCertPath := filepath.Join("..", "misc", "endEntity.der") - endEntityCert, err := os.ReadFile(endEntityCertPath) - require.NoError(t, err, "Failed to read EE certificate") + endEntityCert := testdata.EndEntityDer err = signedCorimIn.AddSigningCert(endEntityCert) require.NoError(t, err, "Failed to add EE certificate") - certChain, err := concatFiles( - filepath.Join("..", "misc", "intermediateCA.der"), - filepath.Join("..", "misc", "rootCA.der")) - require.NoError(t, err, "Failed to read certificate chain") - - err = signedCorimIn.AddIntermediateCerts(certChain) + err = signedCorimIn.AddIntermediateCerts(certChain()) require.NoError(t, err, "Failed to add cert chain") cbor, err := signedCorimIn.Sign(signer) @@ -619,9 +592,7 @@ func TestSignedCorim_SignVerify_with_single_cert_x5chain_ok(t *testing.T) { signedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) signedCorimIn.Meta = *metaGood(t) - endEntityCertPath := filepath.Join("..", "misc", "endEntity.der") - endEntityCert, err := os.ReadFile(endEntityCertPath) - require.NoError(t, err, "Failed to read EE certificate") + endEntityCert := testdata.EndEntityDer err = signedCorimIn.AddSigningCert(endEntityCert) require.NoError(t, err, "Failed to add EE certificate") @@ -655,12 +626,7 @@ func TestSignedCorim_Sign_with_x5chain_fail_missing_ee_cert(t *testing.T) { signedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) signedCorimIn.Meta = *metaGood(t) - certChain, err := concatFiles( - filepath.Join("..", "misc", "intermediateCA.der"), - filepath.Join("..", "misc", "rootCA.der")) - require.NoError(t, err, "Failed to read certificate chain") - - err = signedCorimIn.AddIntermediateCerts(certChain) + err = signedCorimIn.AddIntermediateCerts(certChain()) require.NoError(t, err, "Failed to add cert chain") _, err = signedCorimIn.Sign(signer) diff --git a/corim/signer.go b/corim/signer.go index cf971292..dc2384e8 100644 --- a/corim/signer.go +++ b/corim/signer.go @@ -35,7 +35,7 @@ func (o *Signer) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtSigner: - o.Extensions.Register(v) + o.Register(v) default: return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) } @@ -46,7 +46,7 @@ func (o *Signer) RegisterExtensions(exts extensions.Map) error { // GetExtensions returns previously registered extension func (o *Signer) GetExtensions() extensions.IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } // SetName sets the target Signer's name to the supplied value @@ -89,7 +89,7 @@ func (o Signer) Valid() error { } } - return o.Extensions.validSigner(&o) + return o.validSigner(&o) } // UnmarshalCBOR deserializes from CBOR diff --git a/corim/signer_test.go b/corim/signer_test.go index ec6e0520..6f364c7a 100644 --- a/corim/signer_test.go +++ b/corim/signer_test.go @@ -16,14 +16,14 @@ type signerExtensions struct { func TestSigner_RegisterExtensions(t *testing.T) { signer := NewSigner() - assert.False(t, signer.Extensions.HaveExtensions()) + assert.False(t, signer.HaveExtensions()) exts := &signerExtensions{} extMap := extensions.NewMap().Add(ExtSigner, exts) err := signer.RegisterExtensions(extMap) assert.NoError(t, err) - assert.True(t, signer.Extensions.HaveExtensions()) + assert.True(t, signer.HaveExtensions()) assert.Equal(t, exts, signer.GetExtensions()) badMap := extensions.NewMap().Add(extensions.Point("test"), exts) diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 058c6f99..7203ea5e 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -49,7 +49,7 @@ func (o *UnsignedCorim) RegisterExtensions(exts extensions.Map) error { for p, v := range exts { switch p { case ExtUnsignedCorim: - o.Extensions.Register(v) + o.Register(v) case ExtEntity: if o.Entities == nil { o.Entities = NewEntities() @@ -69,7 +69,7 @@ func (o *UnsignedCorim) RegisterExtensions(exts extensions.Map) error { // GetExtensions returns pervisouosly registered extension func (o *UnsignedCorim) GetExtensions() extensions.IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } // SetID sets the corim-id in the unsigned-corim-map to the supplied value. The @@ -276,7 +276,7 @@ func (o UnsignedCorim) Valid() error { } } - return o.Extensions.validCorim(&o) + return o.validCorim(&o) } // ToCBOR serializes the target unsigned CoRIM to CBOR diff --git a/extensions/collection_test.go b/extensions/collection_test.go index f886934c..e5d9d327 100644 --- a/extensions/collection_test.go +++ b/extensions/collection_test.go @@ -27,7 +27,7 @@ func (o *testExtensible) RegisterExtensions(exts Map) error { for p, v := range exts { switch p { case testPoint: - o.Extensions.Register(v) + o.Register(v) default: return fmt.Errorf("%w: %q", ErrUnexpectedPoint, p) } @@ -37,7 +37,7 @@ func (o *testExtensible) RegisterExtensions(exts Map) error { } func (o testExtensible) GetExtensions() IMapValue { - return o.Extensions.IMapValue + return o.IMapValue } func (o testExtensible) Valid() error { @@ -83,7 +83,7 @@ func Test_Collection_JSON(t *testing.T) { require.NoError(t, err) // as we've not registred the extensions, the unknown field will be ignored. - decoded, err := c.Values[0].Extensions.GetString("field-two") + decoded, err := c.Values[0].GetString("field-two") assert.Equal(t, "", decoded) assert.EqualError(t, err, "extension not found: field-two") @@ -94,7 +94,7 @@ func Test_Collection_JSON(t *testing.T) { err = json.Unmarshal(testData, c) require.NoError(t, err) - decoded, err = c.Values[0].Extensions.GetString("field-two") + decoded, err = c.Values[0].GetString("field-two") require.NoError(t, err) assert.Equal(t, "bar", decoded) @@ -115,7 +115,7 @@ func Test_Collection_CBOR(t *testing.T) { require.NoError(t, err) // as we've not registred the extensions, the unknown field will be ignored. - decoded, err := c.Values[0].Extensions.GetString("field-two") + decoded, err := c.Values[0].GetString("field-two") assert.Equal(t, "", decoded) assert.EqualError(t, err, "extension not found: field-two") @@ -126,7 +126,7 @@ func Test_Collection_CBOR(t *testing.T) { err = dm.Unmarshal(testData, c) require.NoError(t, err) - decoded, err = c.Values[0].Extensions.GetString("field-two") + decoded, err = c.Values[0].GetString("field-two") require.NoError(t, err) assert.Equal(t, "bar", decoded) diff --git a/go.mod b/go.mod index cce1748c..9a5881ba 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,16 @@ module github.com/veraison/corim -go 1.22.0 +go 1.23.0 + +toolchain go1.24.2 require ( + fortio.org/safecast v1.0.0 github.com/fxamacker/cbor/v2 v2.5.0 github.com/google/uuid v1.3.0 github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/spf13/cast v1.4.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff github.com/veraison/go-cose v1.2.1 github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca diff --git a/go.sum b/go.sum index 2aaa8ae4..6515a1c5 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +fortio.org/safecast v1.0.0 h1:dr3131WPX8iS1pTf76+39WeXbTrerDYLvi9s7Oi3wiY= +fortio.org/safecast v1.0.0/go.mod h1:xZmcPk3vi4kuUFf+tq4SvnlVdwViqf6ZSZl91Jr9Jdg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -38,8 +40,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff h1:r6I2eJL/z8dp5flsQIKHMeDjyV6UO8If3MaVBLvTjF4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= github.com/veraison/go-cose v1.2.1 h1:Gj4x20D0YP79J2+cK3anjGEMwIkg2xX+TKVVGUXwNAc= diff --git a/scripts/gen-certs.sh b/scripts/gen-certs.sh index 5b0d6c4b..102533e2 100644 --- a/scripts/gen-certs.sh +++ b/scripts/gen-certs.sh @@ -8,7 +8,7 @@ INTERMEDIATE_CERT_NAME=intermediateCA END_ENTITY_CERT_NAME=endEntity THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -MISC_DIR="$THIS_DIR/../misc" +MISC_DIR="$THIS_DIR/../testdata" mkdir -p "$MISC_DIR" @@ -199,4 +199,4 @@ case $command in *) echo -e "ERROR: unexpected command: \"$command\" (use -h for help)" ;; -esac \ No newline at end of file +esac diff --git a/misc/endEntity.der b/testdata/endEntity.der similarity index 100% rename from misc/endEntity.der rename to testdata/endEntity.der diff --git a/misc/endEntity.key b/testdata/endEntity.key similarity index 100% rename from misc/endEntity.key rename to testdata/endEntity.key diff --git a/misc/intermediateCA.der b/testdata/intermediateCA.der similarity index 100% rename from misc/intermediateCA.der rename to testdata/intermediateCA.der diff --git a/misc/rootCA.der b/testdata/rootCA.der similarity index 100% rename from misc/rootCA.der rename to testdata/rootCA.der diff --git a/testdata/testdata.go b/testdata/testdata.go new file mode 100644 index 00000000..0a29f56a --- /dev/null +++ b/testdata/testdata.go @@ -0,0 +1,25 @@ +// Copyright 2021-2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package testdata + +import ( + _ "embed" +) + +var ( + //go:embed endEntity.der + // EndEntityDer is an intermediate CA-signed certificate. + EndEntityDer []byte + + //go:embed endEntity.key + // EndEntityKey is a key used for signing CoRIMs. + EndEntityKey []byte + + //go:embed intermediateCA.der + // IntermediateCA is a root-signed intermediate certificate authority key certificate. + IntermediateCA []byte + + //go:embed rootCA.der + // RootCA is a self-signed root certificate authority key certificate. + RootCA []byte +) From c9de6bc9c6dcd92d7d2319e7ca789bbdddc1eae4 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 18 Jun 2025 12:50:35 +0100 Subject: [PATCH 103/110] fix!: serialize tags as tagged bstr The CoRIM spec defines the tags field of corim-map as &(tags: 1) => [ + $concise-tag-type-choice ] where $concise-tag-type-choice /= tagged-concise-swid-tag $concise-tag-type-choice /= tagged-concise-mid-tag $concise-tag-type-choice /= tagged-concise-tl-tag and tagged-concise-swid-tag = #6.505(bytes .cbor concise-swid-tag) tagged-concise-mid-tag = #6.506(bytes .cbor concise-mid-tag) tagged-concise-tl-tag = #6.508(bytes .cbor concise-tl-tag) i.e. tags field is supposed to be an array of one or more tagged byte string, which are themselves CBOR encodings of the corresponding struct. Our UnsignedCorim.Tags was defined as []Tag, where Tag was an alias of []byte. It is an array of serialized tag objects. However, since it is essentially, an array of byte strings, it was serialized as such, resulting in [ + bstr .cbor TAG(OBJECT) ] instead of the expected [ + TAG(bstr .cbor OBJECT) ] i.e. tag ended up being part of the bstr, where as it ought to have been wrapping the bstr. This commit changes the definition of Tag to a struct that associates a tag number with a []byte (it is similar to cbor.Tag except the content is always a []byte rather than any). In CBOR, this type is serialized exactly as a cbor.Tag; in JSON, this includes the tag in the base64-encoded data. BREAKING CHANGE: tags (CoMID, CoSWID, etc) are now serialized as tagged bstr's, rather than plain bstrs (that contained the tag). API-wise, the Tag type has been changed to a struct that associates a tag number with a []byte. Signed-off-by: Sergei Trofimov --- corim/cbor.go | 6 +- corim/common_test.go | 2 +- corim/example_profile_test.go | 4 +- corim/profiles_test.go | 7 +- corim/signedcorim_test.go | 133 +++++++++--------- .../signed-corim-with-extensions.cbor | Bin 687 -> 687 bytes corim/testcases/signed-example-corim.cbor | Bin 690 -> 690 bytes corim/testcases/signed-good-corim.cbor | Bin 613 -> 613 bytes .../testcases/src/corim-with-extensions.yaml | 6 +- corim/testcases/src/example-corim.yaml | 6 +- corim/testcases/src/good-corim.yaml | 6 +- .../unsigned-corim-with-extensions.cbor | Bin 517 -> 517 bytes corim/testcases/unsigned-example-corim.cbor | Bin 520 -> 520 bytes corim/testcases/unsigned-good-corim.cbor | Bin 443 -> 443 bytes corim/unsignedcorim.go | 70 +++++++-- corim/unsignedcorim_test.go | 25 +++- cots/cbor.go | 2 +- 17 files changed, 167 insertions(+), 100 deletions(-) diff --git a/corim/cbor.go b/corim/cbor.go index 3d9df1b9..c47166e2 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -17,9 +17,9 @@ var ( ) var ( - UnsignedCorimTag = []byte{0xd9, 0x01, 0xf5} // 501() - CoswidTag = []byte{0xd9, 0x01, 0xf9} // 505() - ComidTag = []byte{0xd9, 0x01, 0xfa} // 506() + UnsignedCorimTag = []byte{0xd9, 0x01, 0xf5} // 501() + CoswidTag uint64 = 505 + ComidTag uint64 = 506 corimTagsMap = map[uint64]interface{}{ 32: comid.TaggedURI(""), diff --git a/corim/common_test.go b/corim/common_test.go index 12ebe10d..289988f3 100644 --- a/corim/common_test.go +++ b/corim/common_test.go @@ -88,7 +88,7 @@ func assertCoRIMEq(t *testing.T, expected []byte, actual []byte, msgAndArgs ...i for i, expectedTag := range expectedCoRIM.Tags { actualTag := actualCoRIM.Tags[i] - if !assertCBOREq(t, expectedTag, actualTag, msgAndArgs...) { + if !assertCBOREq(t, expectedTag.Content, actualTag.Content, msgAndArgs...) { return false } } diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index b4484443..91b4f9b8 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -91,7 +91,7 @@ func Example_profile_unmarshal() { } extractedComid, err := UnmarshalComidFromCBOR( - extractedCorim.Tags[0], + extractedCorim.Tags[0].Content, extractedCorim.Profile, ) if err != nil { @@ -195,5 +195,5 @@ func Example_profile_marshal() { fmt.Printf("corim: %v", hex.EncodeToString(buf)) // output: - // corim: d901f5a300f6018158d9d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 + // corim: d901f5a300f60181d901fa58d6a40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 } diff --git a/corim/profiles_test.go b/corim/profiles_test.go index 02452bd7..eeff6ea8 100644 --- a/corim/profiles_test.go +++ b/corim/profiles_test.go @@ -120,7 +120,7 @@ func TestProfileManifest_marshaling(t *testing.T) { profileManifest, ok := GetProfileManifest(c.Profile) assert.True(t, ok) - cmd, err := UnmarshalComidFromCBOR(c.Tags[0], c.Profile) + cmd, err := UnmarshalComidFromCBOR(c.Tags[0].Content, c.Profile) assert.NoError(t, err) address := cmd.Entities.Values[0].MustGetString("Address") @@ -133,7 +133,7 @@ func TestProfileManifest_marshaling(t *testing.T) { unregProfID, err := eat.NewProfile("http://example.com") require.NoError(t, err) - cmdNoExt, err := UnmarshalComidFromCBOR(c.Tags[0], unregProfID) + cmdNoExt, err := UnmarshalComidFromCBOR(c.Tags[0].Content, unregProfID) assert.NoError(t, err) address = cmdNoExt.Entities.Values[0].MustGetString("Address") @@ -145,8 +145,7 @@ func TestProfileManifest_marshaling(t *testing.T) { out, err = cmd.ToCBOR() assert.NoError(t, err) - // the first 3 bytes in Tags[0] is the tag indicating CoRIM - assertCBOREq(t, c.Tags[0][3:], out) + assertCBOREq(t, c.Tags[0].Content, out) c, err = UnmarshalUnsignedCorimFromJSON(testUnsignedCorimJSON) assert.NoError(t, err) diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index cea6c17a..27a821db 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -133,75 +133,82 @@ func TestSignedCorim_TaggedFromCOSE_ok(t *testing.T) { / payload / << 501({ 0: "test corim id", 1: [ - h'D901FAA40065656E2D474201A1005043BBE37F2E614B33AED353CFF1428B160281A3006941434D45204C74642E01D8207468747470733A2F2F61636D652E6578616D706C65028300010204A1008182A100A300D90258582061636D652D696D706C656D656E746174696F6E2D69642D303030303030303031016441434D45026A526F616452756E6E657283A200D90258A30162424C0465322E312E30055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A102818201582087428FC522803D31065E7BCE3CF03FE475096631E5E07BBD7A0FDE60C4CF25C7A200D90258A3016450526F540465312E332E35055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A10281820158200263829989B6FD954F72BAAF2FC64BC2E2F01D692D4DE72986EA808F6E99813FA200D90258A3016441526F540465302E312E34055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A1028182015820A3A5E715F0CC574A73C3F9BEBB6BC24F32FFD5B67B387244C2C909DA779A1478' + 506(h'A40065656E2D474201A1005043BBE37F2E614B33AED353CFF1428B160281A3006941434D45204C74642E01D8207468747470733A2F2F61636D652E6578616D706C65028300010204A1008182A100A300D90258582061636D652D696D706C656D656E746174696F6E2D69642D303030303030303031016441434D45026A526F616452756E6E657283A200D90258A30162424C0465322E312E30055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A102818201582087428FC522803D31065E7BCE3CF03FE475096631E5E07BBD7A0FDE60C4CF25C7A200D90258A3016450526F540465312E332E35055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A10281820158200263829989B6FD954F72BAAF2FC64BC2E2F01D692D4DE72986EA808F6E99813FA200D90258A3016441526F540465302E312E34055820ACBB11C7E4DA217205523CE4CE1A245AE1A239AE3C6BFD9E7871F7E5D8BAE86B01A1028182015820A3A5E715F0CC574A73C3F9BEBB6BC24F32FFD5B67B387244C2C909DA779A1478') ] }) >>, / signature / h'deadbeef' ] ))) */ - tv := []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6, 0xd2, 0x84, - 0x58, 0x59, 0xa4, 0x01, 0x26, 0x03, 0x74, 0x61, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2f, 0x72, 0x69, 0x6d, 0x2b, 0x63, 0x62, 0x6f, 0x72, - 0x04, 0x58, 0x24, 0x6d, 0x65, 0x72, 0x69, 0x61, 0x64, - 0x6f, 0x63, 0x2e, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x79, - 0x62, 0x75, 0x63, 0x6b, 0x40, 0x62, 0x75, 0x63, 0x6b, - 0x6c, 0x61, 0x6e, 0x64, 0x2e, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x08, 0x57, 0xa2, 0x00, 0xa1, 0x00, - 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x4c, 0x74, 0x64, - 0x2e, 0x01, 0xa1, 0x01, 0xc1, 0x1a, 0x5f, 0xad, 0x20, - 0x56, 0xa0, 0x59, 0x01, 0xbb, 0xd9, 0x01, 0xf5, 0xa2, - 0x00, 0x6d, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6f, - 0x72, 0x69, 0x6d, 0x20, 0x69, 0x64, 0x01, 0x81, 0x59, - 0x01, 0xa3, 0xd9, 0x01, 0xfa, 0xa4, 0x00, 0x65, 0x65, - 0x6e, 0x2d, 0x47, 0x42, 0x01, 0xa1, 0x00, 0x50, 0x43, - 0xbb, 0xe3, 0x7f, 0x2e, 0x61, 0x4b, 0x33, 0xae, 0xd3, - 0x53, 0xcf, 0xf1, 0x42, 0x8b, 0x16, 0x02, 0x81, 0xa3, - 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x4c, 0x74, - 0x64, 0x2e, 0x01, 0xd8, 0x20, 0x74, 0x68, 0x74, 0x74, - 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x63, 0x6d, 0x65, - 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, - 0x83, 0x00, 0x01, 0x02, 0x04, 0xa1, 0x00, 0x81, 0x82, - 0xa1, 0x00, 0xa3, 0x00, 0xd9, 0x02, 0x58, 0x58, 0x20, - 0x61, 0x63, 0x6d, 0x65, 0x2d, 0x69, 0x6d, 0x70, 0x6c, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2d, 0x69, 0x64, 0x2d, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x31, 0x01, 0x64, 0x41, 0x43, - 0x4d, 0x45, 0x02, 0x6a, 0x52, 0x6f, 0x61, 0x64, 0x52, - 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x83, 0xa2, 0x00, 0xd9, - 0x02, 0x58, 0xa3, 0x01, 0x62, 0x42, 0x4c, 0x04, 0x65, - 0x32, 0x2e, 0x31, 0x2e, 0x30, 0x05, 0x58, 0x20, 0xac, - 0xbb, 0x11, 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, 0x52, - 0x3c, 0xe4, 0xce, 0x1a, 0x24, 0x5a, 0xe1, 0xa2, 0x39, - 0xae, 0x3c, 0x6b, 0xfd, 0x9e, 0x78, 0x71, 0xf7, 0xe5, - 0xd8, 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, 0x81, 0x82, - 0x01, 0x58, 0x20, 0x87, 0x42, 0x8f, 0xc5, 0x22, 0x80, - 0x3d, 0x31, 0x06, 0x5e, 0x7b, 0xce, 0x3c, 0xf0, 0x3f, - 0xe4, 0x75, 0x09, 0x66, 0x31, 0xe5, 0xe0, 0x7b, 0xbd, - 0x7a, 0x0f, 0xde, 0x60, 0xc4, 0xcf, 0x25, 0xc7, 0xa2, - 0x00, 0xd9, 0x02, 0x58, 0xa3, 0x01, 0x64, 0x50, 0x52, - 0x6f, 0x54, 0x04, 0x65, 0x31, 0x2e, 0x33, 0x2e, 0x35, - 0x05, 0x58, 0x20, 0xac, 0xbb, 0x11, 0xc7, 0xe4, 0xda, - 0x21, 0x72, 0x05, 0x52, 0x3c, 0xe4, 0xce, 0x1a, 0x24, - 0x5a, 0xe1, 0xa2, 0x39, 0xae, 0x3c, 0x6b, 0xfd, 0x9e, - 0x78, 0x71, 0xf7, 0xe5, 0xd8, 0xba, 0xe8, 0x6b, 0x01, - 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x02, 0x63, - 0x82, 0x99, 0x89, 0xb6, 0xfd, 0x95, 0x4f, 0x72, 0xba, - 0xaf, 0x2f, 0xc6, 0x4b, 0xc2, 0xe2, 0xf0, 0x1d, 0x69, - 0x2d, 0x4d, 0xe7, 0x29, 0x86, 0xea, 0x80, 0x8f, 0x6e, - 0x99, 0x81, 0x3f, 0xa2, 0x00, 0xd9, 0x02, 0x58, 0xa3, - 0x01, 0x64, 0x41, 0x52, 0x6f, 0x54, 0x04, 0x65, 0x30, - 0x2e, 0x31, 0x2e, 0x34, 0x05, 0x58, 0x20, 0xac, 0xbb, - 0x11, 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, 0x52, 0x3c, - 0xe4, 0xce, 0x1a, 0x24, 0x5a, 0xe1, 0xa2, 0x39, 0xae, - 0x3c, 0x6b, 0xfd, 0x9e, 0x78, 0x71, 0xf7, 0xe5, 0xd8, - 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, 0x81, 0x82, 0x01, - 0x58, 0x20, 0xa3, 0xa5, 0xe7, 0x15, 0xf0, 0xcc, 0x57, - 0x4a, 0x73, 0xc3, 0xf9, 0xbe, 0xbb, 0x6b, 0xc2, 0x4f, - 0x32, 0xff, 0xd5, 0xb6, 0x7b, 0x38, 0x72, 0x44, 0xc2, - 0xc9, 0x09, 0xda, 0x77, 0x9a, 0x14, 0x78, 0x44, 0xde, - 0xad, 0xbe, 0xef} + tv := []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6, 0xd2, + 0x84, 0x58, 0x59, 0xa4, 0x01, 0x26, 0x03, 0x74, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x72, 0x69, 0x6d, 0x2b, + 0x63, 0x62, 0x6f, 0x72, 0x04, 0x58, 0x24, 0x6d, + 0x65, 0x72, 0x69, 0x61, 0x64, 0x6f, 0x63, 0x2e, + 0x62, 0x72, 0x61, 0x6e, 0x64, 0x79, 0x62, 0x75, + 0x63, 0x6b, 0x40, 0x62, 0x75, 0x63, 0x6b, 0x6c, + 0x61, 0x6e, 0x64, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x08, 0x57, 0xa2, 0x00, 0xa1, + 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, 0x20, 0x4c, + 0x74, 0x64, 0x2e, 0x01, 0xa1, 0x01, 0xc1, 0x1a, + 0x5f, 0xad, 0x20, 0x56, 0xa0, 0x59, 0x01, 0xbb, + 0xd9, 0x01, 0xf5, 0xa2, 0x00, 0x6d, 0x74, 0x65, + 0x73, 0x74, 0x20, 0x63, 0x6f, 0x72, 0x69, 0x6d, + 0x20, 0x69, 0x64, 0x01, 0x81, 0xd9, 0x01, 0xfa, + 0x59, 0x01, 0xa0, 0xa4, 0x00, 0x65, 0x65, 0x6e, + 0x2d, 0x47, 0x42, 0x01, 0xa1, 0x00, 0x50, 0x43, + 0xbb, 0xe3, 0x7f, 0x2e, 0x61, 0x4b, 0x33, 0xae, + 0xd3, 0x53, 0xcf, 0xf1, 0x42, 0x8b, 0x16, 0x02, + 0x81, 0xa3, 0x00, 0x69, 0x41, 0x43, 0x4d, 0x45, + 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x01, 0xd8, 0x20, + 0x74, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x02, 0x83, 0x00, + 0x01, 0x02, 0x04, 0xa1, 0x00, 0x81, 0x82, 0xa1, + 0x00, 0xa3, 0x00, 0xd9, 0x02, 0x58, 0x58, 0x20, + 0x61, 0x63, 0x6d, 0x65, 0x2d, 0x69, 0x6d, 0x70, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2d, 0x69, 0x64, 0x2d, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, + 0x01, 0x64, 0x41, 0x43, 0x4d, 0x45, 0x02, 0x6a, + 0x52, 0x6f, 0x61, 0x64, 0x52, 0x75, 0x6e, 0x6e, + 0x65, 0x72, 0x83, 0xa2, 0x00, 0xd9, 0x02, 0x58, + 0xa3, 0x01, 0x62, 0x42, 0x4c, 0x04, 0x65, 0x32, + 0x2e, 0x31, 0x2e, 0x30, 0x05, 0x58, 0x20, 0xac, + 0xbb, 0x11, 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, + 0x52, 0x3c, 0xe4, 0xce, 0x1a, 0x24, 0x5a, 0xe1, + 0xa2, 0x39, 0xae, 0x3c, 0x6b, 0xfd, 0x9e, 0x78, + 0x71, 0xf7, 0xe5, 0xd8, 0xba, 0xe8, 0x6b, 0x01, + 0xa1, 0x02, 0x81, 0x82, 0x01, 0x58, 0x20, 0x87, + 0x42, 0x8f, 0xc5, 0x22, 0x80, 0x3d, 0x31, 0x06, + 0x5e, 0x7b, 0xce, 0x3c, 0xf0, 0x3f, 0xe4, 0x75, + 0x09, 0x66, 0x31, 0xe5, 0xe0, 0x7b, 0xbd, 0x7a, + 0x0f, 0xde, 0x60, 0xc4, 0xcf, 0x25, 0xc7, 0xa2, + 0x00, 0xd9, 0x02, 0x58, 0xa3, 0x01, 0x64, 0x50, + 0x52, 0x6f, 0x54, 0x04, 0x65, 0x31, 0x2e, 0x33, + 0x2e, 0x35, 0x05, 0x58, 0x20, 0xac, 0xbb, 0x11, + 0xc7, 0xe4, 0xda, 0x21, 0x72, 0x05, 0x52, 0x3c, + 0xe4, 0xce, 0x1a, 0x24, 0x5a, 0xe1, 0xa2, 0x39, + 0xae, 0x3c, 0x6b, 0xfd, 0x9e, 0x78, 0x71, 0xf7, + 0xe5, 0xd8, 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, + 0x81, 0x82, 0x01, 0x58, 0x20, 0x02, 0x63, 0x82, + 0x99, 0x89, 0xb6, 0xfd, 0x95, 0x4f, 0x72, 0xba, + 0xaf, 0x2f, 0xc6, 0x4b, 0xc2, 0xe2, 0xf0, 0x1d, + 0x69, 0x2d, 0x4d, 0xe7, 0x29, 0x86, 0xea, 0x80, + 0x8f, 0x6e, 0x99, 0x81, 0x3f, 0xa2, 0x00, 0xd9, + 0x02, 0x58, 0xa3, 0x01, 0x64, 0x41, 0x52, 0x6f, + 0x54, 0x04, 0x65, 0x30, 0x2e, 0x31, 0x2e, 0x34, + 0x05, 0x58, 0x20, 0xac, 0xbb, 0x11, 0xc7, 0xe4, + 0xda, 0x21, 0x72, 0x05, 0x52, 0x3c, 0xe4, 0xce, + 0x1a, 0x24, 0x5a, 0xe1, 0xa2, 0x39, 0xae, 0x3c, + 0x6b, 0xfd, 0x9e, 0x78, 0x71, 0xf7, 0xe5, 0xd8, + 0xba, 0xe8, 0x6b, 0x01, 0xa1, 0x02, 0x81, 0x82, + 0x01, 0x58, 0x20, 0xa3, 0xa5, 0xe7, 0x15, 0xf0, + 0xcc, 0x57, 0x4a, 0x73, 0xc3, 0xf9, 0xbe, 0xbb, + 0x6b, 0xc2, 0x4f, 0x32, 0xff, 0xd5, 0xb6, 0x7b, + 0x38, 0x72, 0x44, 0xc2, 0xc9, 0x09, 0xda, 0x77, + 0x9a, 0x14, 0x78, 0x44, 0xde, 0xad, 0xbe, 0xef} var actual SignedCorim err := actual.FromCOSE(tv) diff --git a/corim/testcases/signed-corim-with-extensions.cbor b/corim/testcases/signed-corim-with-extensions.cbor index 9988c0847b03576ad3098c0a83f2f76ec8d941ee..bdb5d0efb9eaf23d6261f56bbd4a3bb9196fe2a0 100644 GIT binary patch delta 219 zcmZ3_x}J4{iV))>hJ#Xx=R6n~4@zZ*q&F}unrI`T&QRj$?CYxFQ<9=koSB}NnU}7R zom#mdl8N;u*E|GOd z1OZ|~Op&}Rk?S)J0c228Z&UQmpolWG+4BmGLsB_vb!R4pgxWgD)iQ37r~7GHwY+$+uQvD{Z1wPgmr VI&Zv3%*$aMy0wQ%2DphO!uRmgQ#}9x diff --git a/corim/testcases/signed-example-corim.cbor b/corim/testcases/signed-example-corim.cbor index 5749565e2207b6e76e55fadf72109215dc7b45d1..2b701aa554ec904545c9b14728511651d4ec6fd3 100644 GIT binary patch delta 204 zcmV;-05kux1+oQ@CJ+I^8fjE#fB?Z7Va`YZ0HTph7$*R9K|@VNAWU>*AaiMFZfS03 zAZulLpjiS4*#Y&W0fE^8`dI~&18v_D?q>)S*k&t7PCIK!G0NDarqXA+vT^n=IMN9z4%qnZQfL#W#?X$arZsf|0?=h&U~KJ@9PG0en-YG X@epG_et*L(%lb~Hg*z(r9XxFTY+6=! diff --git a/corim/testcases/signed-good-corim.cbor b/corim/testcases/signed-good-corim.cbor index da73cee72254476153377e0c851ebc18492ae355..db905ca1ef9af334183220200458575832eb6d7f 100644 GIT binary patch delta 170 zcmV;b09F6x1my&fHjzviHUM-%Lrp~>Omt)*b7^O8X>MmAYh`($SpmD*0rjE)ZFFUG zbRc7Ia%pWKX=DL`*#Y`l0idLl&H+Y|bwZKfGm*C_lM(?g2Lxp?E;BAQk?$i00c228 zZ&Z_T0bW8_K(EzMVv&)H36V%86ML*bO>q(}))a;Pa1qFyXXod;)(lRp6{lMDeflV<@g z4FP0OQg2iQWic)@E;W-J0V9*e0bY@WMo|E5bY*jNAY*TGX>A~BWLQA&l8pTVT$zj$ z)J3r#4BEYrQzUJzJ=>iLzPI90Nqj~Igk8rI`{ho2|1We$0^aIzFw)Nt1%}bC@y(n( GGLiItv_F6V diff --git a/corim/testcases/src/corim-with-extensions.yaml b/corim/testcases/src/corim-with-extensions.yaml index f07e38c0..cc14dd8e 100644 --- a/corim/testcases/src/corim-with-extensions.yaml +++ b/corim/testcases/src/corim-with-extensions.yaml @@ -9,9 +9,9 @@ value: 3: http://example.com/test-profile -1: foo 1: - - encodedCBOR: - tag: 506 - value: + - tag: 506 + value: + encodedCBOR: 0: en-GB 1: 0: !!binary |- diff --git a/corim/testcases/src/example-corim.yaml b/corim/testcases/src/example-corim.yaml index a31b0bad..1b9dd2ef 100644 --- a/corim/testcases/src/example-corim.yaml +++ b/corim/testcases/src/example-corim.yaml @@ -12,9 +12,9 @@ value: 3: http://example.com/example-profile -1: foo 1: - - encodedCBOR: - tag: 506 - value: + - tag: 506 + value: + encodedCBOR: 0: en-GB 1: 0: !!binary |- diff --git a/corim/testcases/src/good-corim.yaml b/corim/testcases/src/good-corim.yaml index 932d5fff..f5601bbc 100644 --- a/corim/testcases/src/good-corim.yaml +++ b/corim/testcases/src/good-corim.yaml @@ -4,9 +4,9 @@ tag: 501 value: 0: test corim id 1: - - encodedCBOR: - tag: 506 - value: + - tag: 506 + value: + encodedCBOR: 0: en-GB 1: 0: !!binary |- diff --git a/corim/testcases/unsigned-corim-with-extensions.cbor b/corim/testcases/unsigned-corim-with-extensions.cbor index 37fa07cfb56ca84c9977d21b2ee257314534ac48..0eea23b5256c75a50d77d01046539a070c72af0d 100644 GIT binary patch delta 113 zcmZo=X=M?-$@q1NLULMuK4are#$S<)hbQvLPjr`^s4XMHvXG&%X(7X6rmUd+#FU`Y zyu8$+i8(q<&5I^pG3H=gq#%_xkvDJR3N^Mx3^$n~7f%*u)MiZy2+9wcY|CiE#+c#= RViq!LPqt%ZpS+SW901|;CFTGC delta 115 zcmV-(0F3{I1cd|)*#Y&W0fAWo#@PY-kr5w}O&pOh9S8)W0D*#`0Hcw8ED!=}Qg2~o zQgv=_Wpaa}0iu!SCI%oHW|0kUk*hNkq5#(7)1_5MHQg2j~4FM~YJ^^KusR1SiAR1hyf;(&jC45gC02m diff --git a/corim/testcases/unsigned-good-corim.cbor b/corim/testcases/unsigned-good-corim.cbor index d387b7b62f90ea4efce22daf04c2611a80f9c4c2..ba02c3cf1cd8a1835d95cd8fa1ef752a0184e7ff 100644 GIT binary patch delta 113 zcmdnZyqkG~sLxHtUy+OpmM}FgW@=_&WMas4boO;s@F_{rW4xhIl2KApP;8~IpO~DR zs+U@km|KvO%CeB5v1uX0V#XAZQl_k+{KS-?(!9LXqKR2*6Bi}3v!oj88S9x&&SF$$ Q2?)v$nQY3~Hc>_z0Cq(r`2YX_ delta 90 zcmdnZyqkG~D0?L1;+u@WmP}s2D9y~&xOk$zgalJF10xedrlYg3tAbBSiXO{ChQ_9a t42vfwstGZsfP|T{g7Onnf=cu9Qi~?GCr|vS$ Date: Thu, 19 Jun 2025 12:19:14 +0100 Subject: [PATCH 104/110] fix: validate inside UnsignedCorim.ToCBOR() (#204) Validate before serializing inside UnsignedCorim.ToCBOR() to be consistent with the semantics of ToCBOR() in other types. Signed-off-by: Sergei Trofimov --- corim/example_profile_test.go | 5 +++-- corim/unsignedcorim.go | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index 91b4f9b8..69b6ce86 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -138,7 +138,6 @@ func Example_profile_marshal() { log.Fatalf("profile %v not found", profileID) } - myCorim := profileManifest.GetUnsignedCorim() myComid := profileManifest.GetComid(). SetLanguage("en-GB"). SetTagIdentity("example", 0). @@ -185,6 +184,8 @@ func Example_profile_marshal() { log.Fatalf("comid validity: %v", err) } + myCorim := profileManifest.GetUnsignedCorim() + myCorim.SetID("foo") myCorim.AddComid(myComid) buf, err := myCorim.ToCBOR() @@ -195,5 +196,5 @@ func Example_profile_marshal() { fmt.Printf("corim: %v", hex.EncodeToString(buf)) // output: - // corim: d901f5a300f60181d901fa58d6a40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 + // corim: d901f5a30063666f6f0181d901fa58d6a40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 } diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 6e9d6db3..e33282fb 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -280,6 +280,10 @@ func (o UnsignedCorim) Valid() error { // ToCBOR serializes the target unsigned CoRIM to CBOR // nolint:gocritic func (o UnsignedCorim) ToCBOR() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + // If extensions have been registered, the collection will exist, but // might be empty. If that is the case, set it to nil to avoid // marshaling an empty list (and let the marshaller omit the claim From b88a37ba4c074b604557c35f367a3f1c92018fed Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Wed, 18 Jun 2025 16:23:58 +0200 Subject: [PATCH 105/110] fix(ci): align go version across workloads Signed-off-by: Thomas Fossati --- .github/workflows/ci-go-cover.yml | 5 +++-- .github/workflows/ci.yml | 2 +- .github/workflows/linters.yml | 2 +- scripts/cov.py | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-go-cover.yml b/.github/workflows/ci-go-cover.yml index 7d391e88..682523eb 100644 --- a/.github/workflows/ci-go-cover.yml +++ b/.github/workflows/ci-go-cover.yml @@ -24,9 +24,9 @@ jobs: name: Coverage runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: - go-version: "1.22" + go-version: "1.23" - name: Checkout code uses: actions/checkout@v2 - name: Install mockgen @@ -37,3 +37,4 @@ jobs: go version make test-cover | grep -o "coverage:.*of statements$" | python scripts/cov.py shell: bash + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 022afead..05036f75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: "1.22" + go-version: "1.23" - name: Checkout code uses: actions/checkout@v2 with: diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index cbd8831c..7e9d2ea2 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -8,7 +8,7 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 with: go-version: "1.23" - name: Checkout code diff --git a/scripts/cov.py b/scripts/cov.py index 8b19fabe..1b13b335 100644 --- a/scripts/cov.py +++ b/scripts/cov.py @@ -17,6 +17,7 @@ for l in cover_report_lines.splitlines(): cover = float(re.findall(r'\d*\.\d+|\d+', l)[0]) if cover < min_cover: + print("want", min_cover, "got", cover) sys.exit(1) sys.exit(0) From 6fe50dc031ebee407fecb62335135c92914944c3 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Wed, 18 Jun 2025 16:25:26 +0200 Subject: [PATCH 106/110] fix(comid): small fixes * add NewClassBytes * remove unused MustNewCryptoKeyTaggedBytes * fix typo Signed-off-by: Thomas Fossati --- comid/class.go | 10 ++++++++++ comid/cryptokey.go | 9 --------- comid/psareferencevalue_test.go | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/comid/class.go b/comid/class.go index f05b2496..0eea5567 100644 --- a/comid/class.go +++ b/comid/class.go @@ -52,6 +52,16 @@ func NewClassOID(oid string) *Class { return &Class{ClassID: classID} } +// NewClassBytes instantiates a new Class object with tagged-bytes ID +func NewClassBytes(b any) *Class { + classID, err := NewBytesClassID(b) + if err != nil { + return nil + } + + return &Class{ClassID: classID} +} + // SetVendor sets the vendor metadata to the supplied string func (o *Class) SetVendor(vendor string) *Class { if o != nil { diff --git a/comid/cryptokey.go b/comid/cryptokey.go index 4d2a4d1b..0e294bef 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -773,12 +773,3 @@ func NewCryptoKeyTaggedBytes(val any) (*CryptoKey, error) { return &CryptoKey{tb}, nil } - -func MustNewCryptoKeyTaggedBytes(val any) *CryptoKey { - key, err := NewCryptoKeyTaggedBytes(val) - if err != nil { - panic(err) - } - - return key -} diff --git a/comid/psareferencevalue_test.go b/comid/psareferencevalue_test.go index 0207becd..f8fa5314 100644 --- a/comid/psareferencevalue_test.go +++ b/comid/psareferencevalue_test.go @@ -34,7 +34,7 @@ func TestPSARefValID_Valid_SignerID_range(t *testing.T) { } } -func TestPSARefValID_Streing(t *testing.T) { +func TestPSARefValID_String(t *testing.T) { signerID := MustHexDecode(t, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") refvalID, err := NewTaggedPSARefValID(signerID) require.NoError(t, err) From f48404b84d34cd52e08fda1d6b826328a668e200 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Wed, 18 Jun 2025 16:26:38 +0200 Subject: [PATCH 107/110] feat(coserv): implement draft-howard-rats-coserv-02 * base framework * support for ref-values * support for attestation verification keys Signed-off-by: Thomas Fossati --- Makefile | 1 + coserv/artifacttype.go | 26 ++ coserv/coserv.go | 128 ++++++ coserv/coserv_test.go | 381 ++++++++++++++++++ coserv/environmentselector.go | 73 ++++ coserv/environmentselector_test.go | 24 ++ coserv/quads.go | 16 + coserv/query.go | 61 +++ coserv/query_test.go | 34 ++ coserv/resultset.go | 74 ++++ coserv/resultset_test.go | 41 ++ coserv/resulttype.go | 26 ++ coserv/test_common.go | 158 ++++++++ coserv/testvectors/Makefile | 20 + .../example-class-selector-noindent.b64u | 1 + .../example-class-selector-noindent.cbor | Bin 0 -> 137 bytes .../example-class-selector-noindent.diag | 1 + .../example-class-selector-noindent.hex | 1 + .../testvectors/example-class-selector.b64u | 1 + .../testvectors/example-class-selector.cbor | Bin 0 -> 137 bytes .../testvectors/example-class-selector.diag | 20 + coserv/testvectors/example-class-selector.hex | 1 + .../testvectors/example-group-selector.b64u | 1 + .../testvectors/example-group-selector.cbor | Bin 0 -> 103 bytes .../testvectors/example-group-selector.diag | 14 + coserv/testvectors/example-group-selector.hex | 1 + .../example-instance-selector.b64u | 1 + .../example-instance-selector.cbor | Bin 0 -> 95 bytes .../example-instance-selector.diag | 14 + .../testvectors/example-instance-selector.hex | 1 + ...class-simple-results-source-artifacts.b64u | 1 + ...class-simple-results-source-artifacts.cbor | Bin 0 -> 223 bytes ...class-simple-results-source-artifacts.diag | 25 ++ ...-class-simple-results-source-artifacts.hex | 1 + .../testvectors/rv-class-simple-results.b64u | 1 + .../testvectors/rv-class-simple-results.cbor | Bin 0 -> 251 bytes .../testvectors/rv-class-simple-results.diag | 54 +++ .../testvectors/rv-class-simple-results.hex | 1 + coserv/testvectors/rv-class-simple.b64u | 1 + coserv/testvectors/rv-class-simple.cbor | Bin 0 -> 116 bytes coserv/testvectors/rv-class-simple.diag | 17 + coserv/testvectors/rv-class-simple.hex | 1 + coserv/testvectors/rv-results.b64u | 1 + coserv/testvectors/rv-results.cbor | Bin 0 -> 180 bytes coserv/testvectors/rv-results.diag | 42 ++ coserv/testvectors/rv-results.hex | 1 + go.mod | 3 +- go.sum | 4 + 48 files changed, 1272 insertions(+), 1 deletion(-) create mode 100644 coserv/artifacttype.go create mode 100644 coserv/coserv.go create mode 100644 coserv/coserv_test.go create mode 100644 coserv/environmentselector.go create mode 100644 coserv/environmentselector_test.go create mode 100644 coserv/quads.go create mode 100644 coserv/query.go create mode 100644 coserv/query_test.go create mode 100644 coserv/resultset.go create mode 100644 coserv/resultset_test.go create mode 100644 coserv/resulttype.go create mode 100644 coserv/test_common.go create mode 100644 coserv/testvectors/Makefile create mode 100644 coserv/testvectors/example-class-selector-noindent.b64u create mode 100644 coserv/testvectors/example-class-selector-noindent.cbor create mode 100644 coserv/testvectors/example-class-selector-noindent.diag create mode 100644 coserv/testvectors/example-class-selector-noindent.hex create mode 100644 coserv/testvectors/example-class-selector.b64u create mode 100644 coserv/testvectors/example-class-selector.cbor create mode 100644 coserv/testvectors/example-class-selector.diag create mode 100644 coserv/testvectors/example-class-selector.hex create mode 100644 coserv/testvectors/example-group-selector.b64u create mode 100644 coserv/testvectors/example-group-selector.cbor create mode 100644 coserv/testvectors/example-group-selector.diag create mode 100644 coserv/testvectors/example-group-selector.hex create mode 100644 coserv/testvectors/example-instance-selector.b64u create mode 100644 coserv/testvectors/example-instance-selector.cbor create mode 100644 coserv/testvectors/example-instance-selector.diag create mode 100644 coserv/testvectors/example-instance-selector.hex create mode 100644 coserv/testvectors/rv-class-simple-results-source-artifacts.b64u create mode 100644 coserv/testvectors/rv-class-simple-results-source-artifacts.cbor create mode 100644 coserv/testvectors/rv-class-simple-results-source-artifacts.diag create mode 100644 coserv/testvectors/rv-class-simple-results-source-artifacts.hex create mode 100644 coserv/testvectors/rv-class-simple-results.b64u create mode 100644 coserv/testvectors/rv-class-simple-results.cbor create mode 100644 coserv/testvectors/rv-class-simple-results.diag create mode 100644 coserv/testvectors/rv-class-simple-results.hex create mode 100644 coserv/testvectors/rv-class-simple.b64u create mode 100644 coserv/testvectors/rv-class-simple.cbor create mode 100644 coserv/testvectors/rv-class-simple.diag create mode 100644 coserv/testvectors/rv-class-simple.hex create mode 100644 coserv/testvectors/rv-results.b64u create mode 100644 coserv/testvectors/rv-results.cbor create mode 100644 coserv/testvectors/rv-results.diag create mode 100644 coserv/testvectors/rv-results.hex diff --git a/Makefile b/Makefile index 72f7fcdd..75956505 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ GOPKG += github.com/veraison/corim/comid GOPKG += github.com/veraison/corim/cots GOPKG += github.com/veraison/corim/encoding GOPKG += github.com/veraison/corim/extensions +GOPKG += github.com/veraison/corim/coserv GOLINT ?= golangci-lint diff --git a/coserv/artifacttype.go b/coserv/artifacttype.go new file mode 100644 index 00000000..f30d39fd --- /dev/null +++ b/coserv/artifacttype.go @@ -0,0 +1,26 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +type ArtifactType uint8 + +const ( + ArtifactTypeEndorsedValues ArtifactType = iota + ArtifactTypeTrustAnchors + ArtifactTypeReferenceValues +) + +// String returns the string representation of the target ArtifactType +func (a ArtifactType) String() string { + switch a { + case ArtifactTypeEndorsedValues: + return "endorsed-values" + case ArtifactTypeReferenceValues: + return "reference-values" + case ArtifactTypeTrustAnchors: + return "trust-anchors" + } + // unreachable + return "" +} diff --git a/coserv/coserv.go b/coserv/coserv.go new file mode 100644 index 00000000..843c9fde --- /dev/null +++ b/coserv/coserv.go @@ -0,0 +1,128 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +// Package coserv provides an implementation of draft-howard-rats-coserv +package coserv + +import ( + "encoding/base64" + "fmt" + + "github.com/fxamacker/cbor/v2" + "github.com/veraison/eat" +) + +// Coserv is the internal representation of a CoSERV data item +type Coserv struct { + Profile eat.Profile `cbor:"0,keyasint"` + Query Query `cbor:"1,keyasint"` + Results *ResultSet `cbor:"2,keyasint,omitempty"` +} + +// NewCoserv creates a new Coserv instance. +// An error is returned if the supplied profile or query are invalid +func NewCoserv(profile string, query Query) (*Coserv, error) { + p, err := eat.NewProfile(profile) + if err != nil { + return nil, fmt.Errorf("invalid profile: %w", err) + } + + if err := query.Valid(); err != nil { + return nil, fmt.Errorf("invalid query: %w", err) + } + + return &Coserv{ + Profile: *p, + Query: query, + }, nil +} + +// AddResults add the result set to the Coserv target after validating it +func (o *Coserv) AddResults(v ResultSet) error { + if err := v.Valid(); err != nil { + return fmt.Errorf("invalid result set: %w", err) + } + + o.Results = &v + + return nil +} + +// ToEDN encodes the target Coserv to CBOR Extended Diagnostic Notation (EDN) +func (o Coserv) ToEDN() (string, error) { // nolint:gocritic + b, err := o.ToCBOR() + if err != nil { + return "", fmt.Errorf("failed encoding Coserv object: %w", err) + } + return cbor.Diagnose(b) +} + +// Valid ensures that the Coserv target is correctly populated +func (o Coserv) Valid() error { // nolint:gocritic + // TBC: + // * artifact type mismatch should be caught on decoding + // * ditto for profile syntax errors + if err := o.Query.Valid(); err != nil { + return fmt.Errorf("invalid query: %w", err) + } + return nil +} + +// ToCBOR validates and serializes to CBOR the target Coserv +// An error is returned if either validation or encoding of the Coserv target fails +func (o Coserv) ToCBOR() ([]byte, error) { // nolint:gocritic + if err := o.Valid(); err != nil { + return nil, fmt.Errorf("validating Coserv: %w", err) + } + + opts := cbor.CoreDetEncOptions() + opts.Time = cbor.TimeRFC3339 + opts.TimeTag = 1 + em, err := opts.EncMode() + if err != nil { + return nil, fmt.Errorf("CBOR encoding setup failed: %w", err) + } + + data, err := em.Marshal(o) + if err != nil { + return nil, fmt.Errorf("encoding Coserv to CBOR: %w", err) + } + + return data, nil +} + +// FromCBOR deserializes from CBOR into the target Coserv +// An error is returned if either decoding or validation of the CoSERV payload fails +func (o *Coserv) FromCBOR(data []byte) error { + if err := cbor.Unmarshal(data, o); err != nil { + return fmt.Errorf("decoding CoSERV from CBOR: %w", err) + } + + if err := o.Valid(); err != nil { + return fmt.Errorf("validating CoSERV: %w", err) + } + + return nil +} + +// ToBase64Url validates and serializes to base64url the target Coserv +// An error is returned if either validation or encoding of the Coserv target fails +func (o Coserv) ToBase64Url() (string, error) { // nolint:gocritic + data, err := o.ToCBOR() + if err != nil { + return "", err + } + + return base64.RawURLEncoding.EncodeToString(data), nil +} + +// FromBase64Url deserializes from base64url-encoded into the target Coserv +// An error is returned if either decoding or validation of the CoSERV payload fails +func (o *Coserv) FromBase64Url(s string) error { + data, err := base64.RawURLEncoding.DecodeString(s) + if err != nil { + return fmt.Errorf("decoding CoSERV: %w", err) + } + + return o.FromCBOR(data) +} diff --git a/coserv/coserv_test.go b/coserv/coserv_test.go new file mode 100644 index 00000000..8c99f261 --- /dev/null +++ b/coserv/coserv_test.go @@ -0,0 +1,381 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" + "github.com/veraison/swid" +) + +func TestCoserv_ToCBOR_rv_class_simple(t *testing.T) { + class := comid.NewClassBytes([]byte{0x00, 0x11, 0x22, 0x33}). + SetVendor("Example Vendor"). + SetModel("Example Model") + require.NotNil(t, class) + + envSelector := NewEnvironmentSelector(). + AddClass(*class) + require.NotNil(t, envSelector) + + query, err := NewQuery(ArtifactTypeReferenceValues, *envSelector, ResultTypeSourceArtifacts) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + actual, err := tv.ToCBOR() + assert.NoError(t, err) + + expected := readTestVectorSlice(t, "rv-class-simple.cbor") + assert.Equal(t, expected, actual) +} + +func TestCoserv_ToCBOR_exampleClassSelector(t *testing.T) { + query, err := NewQuery(ArtifactTypeReferenceValues, *exampleClassSelector(t), ResultTypeCollectedArtifacts) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + actual, err := tv.ToCBOR() + assert.NoError(t, err) + + fmt.Printf("%x\n", actual) + + expected := readTestVectorSlice(t, "example-class-selector.cbor") + assert.Equal(t, expected, actual) + + fmt.Printf("%x\n", expected) +} + +func TestCoserv_ToCBOR_exampleInstanceSelector(t *testing.T) { + query, err := NewQuery(ArtifactTypeReferenceValues, *exampleInstanceSelector(t), ResultTypeBoth) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + actual, err := tv.ToCBOR() + assert.NoError(t, err) + + expected := readTestVectorSlice(t, "example-instance-selector.cbor") + assert.Equal(t, expected, actual) +} + +func TestCoserv_ToCBOR_exampleGroupSelector(t *testing.T) { + query, err := NewQuery(ArtifactTypeReferenceValues, *exampleGroupSelector(t), ResultTypeSourceArtifacts) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + actual, err := tv.ToCBOR() + assert.NoError(t, err) + + fmt.Printf("%x\n", actual) + + expected := readTestVectorSlice(t, "example-group-selector.cbor") + assert.Equal(t, expected, actual) +} + +func TestCoserv_FromCBOR_fail(t *testing.T) { + tv := comid.MustHexDecode(t, "ff") + + var actual Coserv + err := actual.FromCBOR(tv) + assert.EqualError(t, err, `decoding CoSERV from CBOR: cbor: unexpected "break" code`) +} + +func TestCoserv_FromBase64Url_ok_class(t *testing.T) { + tv := readTestVectorString(t, "example-class-selector.b64u") + + var actual Coserv + + err := actual.FromBase64Url(tv) + require.NoError(t, err) + + actualProfile, err := actual.Profile.Get() + require.NoError(t, err) + assert.Equal(t, `tag:example.com,2025:cc-platform#1.0.0`, actualProfile) + assert.Equal(t, "reference-values", actual.Query.ArtifactType.String()) + assert.Equal(t, testTimestamp, actual.Query.Timestamp) + assert.Equal(t, "collected-artifacts", actual.Query.ResultType.String()) + assert.Equal(t, *exampleClassSelector(t), actual.Query.EnvironmentSelector) +} + +func TestCoserv_FromBase64Url_ok_instance(t *testing.T) { + tv := readTestVectorString(t, "example-instance-selector.b64u") + + var actual Coserv + + err := actual.FromBase64Url(tv) + require.NoError(t, err) + + actualProfile, err := actual.Profile.Get() + require.NoError(t, err) + assert.Equal(t, `tag:example.com,2025:cc-platform#1.0.0`, actualProfile) + assert.Equal(t, "reference-values", actual.Query.ArtifactType.String()) + assert.Equal(t, testTimestamp, actual.Query.Timestamp) + assert.Equal(t, "both", actual.Query.ResultType.String()) + assert.Equal(t, *exampleInstanceSelector(t), actual.Query.EnvironmentSelector) +} + +func TestCoserv_FromBase64Url_ok_group(t *testing.T) { + tv := readTestVectorString(t, "example-group-selector.b64u") + + var actual Coserv + + err := actual.FromBase64Url(tv) + require.NoError(t, err) + + actualProfile, err := actual.Profile.Get() + require.NoError(t, err) + assert.Equal(t, `tag:example.com,2025:cc-platform#1.0.0`, actualProfile) + assert.Equal(t, "reference-values", actual.Query.ArtifactType.String()) + assert.Equal(t, testTimestamp, actual.Query.Timestamp) + assert.Equal(t, "source-artifacts", actual.Query.ResultType.String()) + assert.Equal(t, *exampleGroupSelector(t), actual.Query.EnvironmentSelector) +} + +func TestCoserv_FromBase64Url_fail(t *testing.T) { + tv := "=/+" + + var actual Coserv + + err := actual.FromBase64Url(tv) + assert.EqualError(t, err, "decoding CoSERV: illegal base64 data at input byte 0") +} + +func TestCoserv_ToBase64Url_ok_instance(t *testing.T) { + query, err := NewQuery(ArtifactTypeReferenceValues, *exampleInstanceSelector(t), ResultTypeBoth) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + actual, err := tv.ToBase64Url() + assert.NoError(t, err) + + fmt.Printf("%s\n", actual) + + expected := readTestVectorString(t, "example-instance-selector.b64u") + + assert.Equal(t, expected, actual) +} + +func TestCoserv_ToBase64Url_ok_class(t *testing.T) { + query, err := NewQuery(ArtifactTypeReferenceValues, *exampleClassSelector(t), ResultTypeCollectedArtifacts) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + actual, err := tv.ToBase64Url() + assert.NoError(t, err) + + fmt.Printf("%s\n", actual) + + expected := readTestVectorString(t, "example-class-selector.b64u") + + assert.Equal(t, expected, actual) +} + +func TestCoserv_ToBase64Url_ok_group(t *testing.T) { + query, err := NewQuery(ArtifactTypeReferenceValues, *exampleGroupSelector(t), ResultTypeSourceArtifacts) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + actual, err := tv.ToBase64Url() + assert.NoError(t, err) + + fmt.Printf("%s\n", actual) + + expected := readTestVectorString(t, "example-group-selector.b64u") + + assert.Equal(t, expected, actual) +} + +func TestCoserv_ToEDN_ok(t *testing.T) { + query, err := NewQuery(ArtifactTypeReferenceValues, *exampleClassSelector(t), ResultTypeCollectedArtifacts) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + actual, err := tv.ToEDN() + require.NoError(t, err) + + expected := readTestVectorString(t, "example-class-selector-noindent.diag") + assert.Equal(t, expected, actual) +} + +func TestCoserv_FromCBOR_Results(t *testing.T) { + tv := readTestVectorSlice(t, "rv-class-simple-results.cbor") + + var actual Coserv + + err := actual.FromCBOR(tv) + require.NoError(t, err) + + actualProfile, err := actual.Profile.Get() + require.NoError(t, err) + assert.Equal(t, `tag:example.com,2025:cc-platform#1.0.0`, actualProfile) + assert.Equal(t, "reference-values", actual.Query.ArtifactType.String()) + assert.Equal(t, testTimestamp, actual.Query.Timestamp) + assert.Equal(t, *exampleClassSelector2(t), actual.Query.EnvironmentSelector) + + // results-related assertions + assert.NotNil(t, actual.Results) + assert.NotNil(t, actual.Results.Expiry) + assert.Equal(t, *actual.Results.Expiry, testExpiry) + assert.NotNil(t, actual.Results.RVQ) + assert.Len(t, *actual.Results.RVQ, 1) + + assert.Equal(t, testBytes, (*actual.Results.RVQ)[0].RVTriple.Environment.Class.ClassID.Bytes()) + + assert.Len(t, (*actual.Results.RVQ)[0].RVTriple.Measurements.Values, 2) + assert.Equal(t, "Component A", *(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Name) + assert.Equal(t, "Component B", *(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Name) + + assert.Len(t, *(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests, 2) + + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[0].HashAlgID, swid.Sha256) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[0].HashValue, []byte{0xaa}) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[1].HashAlgID, swid.Sha256_128) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[1].HashValue, []byte{0xbb}) + + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[0].HashAlgID, swid.Sha256) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[0].HashValue, []byte{0xcc}) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[1].HashAlgID, swid.Sha256_128) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[1].HashValue, []byte{0xdd}) +} + +func TestCoserv_FromCBOR_Results_Source_Artifacts(t *testing.T) { + tv := readTestVectorSlice(t, "rv-class-simple-results-source-artifacts.cbor") + + var actual Coserv + + err := actual.FromCBOR(tv) + require.NoError(t, err) + + assert.Equal(t, "source-artifacts", actual.Query.ResultType.String()) + + // results-related assertions + assert.NotNil(t, actual.Results) + + assert.NotNil(t, actual.Results.RVQ) + assert.Len(t, *actual.Results.RVQ, 0) + + assert.NotNil(t, actual.Results.SourceArtifacts) + assert.Len(t, *actual.Results.SourceArtifacts, 2) + + cmw0 := (*actual.Results.SourceArtifacts)[0] + + assert.Equal(t, "monad", cmw0.GetKind().String()) + + t0, err := cmw0.GetMonadType() + require.NoError(t, err) + assert.Equal(t, "application/vnd.example.refvals", t0) + + v0, err := cmw0.GetMonadValue() + require.NoError(t, err) + assert.Equal(t, []byte{0xaf, 0xae, 0xad, 0xac}, v0) + + cmw1 := (*actual.Results.SourceArtifacts)[1] + + assert.Equal(t, "monad", cmw1.GetKind().String()) + + t1, err := cmw1.GetMonadType() + require.NoError(t, err) + assert.Equal(t, "application/vnd.example.refvals", t1) + + v1, err := cmw1.GetMonadValue() + require.NoError(t, err) + assert.Equal(t, []byte{0xad, 0xac, 0xab, 0xaa}, v1) +} + +func TestCoserv_results_ToCBOR_ok(t *testing.T) { + class := comid.NewClassBytes(comid.TestBytes) + require.NotNil(t, class) + + envSelector := NewEnvironmentSelector(). + AddClass(*class) + require.NotNil(t, envSelector) + + query, err := NewQuery(ArtifactTypeReferenceValues, *envSelector, ResultTypeCollectedArtifacts) + require.NoError(t, err) + + // overwrite the default query timestamp + query.SetTimestamp(testTimestamp) + + tv, err := NewCoserv( + `tag:example.com,2025:cc-platform#1.0.0`, + *query, + ) + require.NoError(t, err) + + err = tv.AddResults(*exampleReferenceValuesResultSet(t)) + require.NoError(t, err) + + expected := readTestVectorSlice(t, "rv-results.cbor") + + actual, err := tv.ToCBOR() + assert.NoError(t, err) + assert.Equal(t, expected, actual) +} diff --git a/coserv/environmentselector.go b/coserv/environmentselector.go new file mode 100644 index 00000000..e25436fc --- /dev/null +++ b/coserv/environmentselector.go @@ -0,0 +1,73 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import ( + "errors" + + "github.com/veraison/corim/comid" +) + +type EnvironmentSelector struct { + Classes *[]comid.Class `cbor:"0,keyasint,omitempty"` + Instances *[]comid.Instance `cbor:"1,keyasint,omitempty"` + Groups *[]comid.Group `cbor:"2,keyasint,omitempty"` +} + +// NewEnvironmentSelector creates a new EnvironmentSelector instance +func NewEnvironmentSelector() *EnvironmentSelector { + return &EnvironmentSelector{} +} + +// AddClass adds the supplied CoMID class to the target EnvironmentSelector +func (o *EnvironmentSelector) AddClass(v comid.Class) *EnvironmentSelector { + if o.Classes == nil { + o.Classes = new([]comid.Class) + } + + *o.Classes = append(*o.Classes, v) + + return o +} + +// AddInstance adds the supplied CoMID instance to the target EnvironmentSelector +func (o *EnvironmentSelector) AddInstance(v comid.Instance) *EnvironmentSelector { + if o.Instances == nil { + o.Instances = new([]comid.Instance) + } + + *o.Instances = append(*o.Instances, v) + + return o +} + +// AddGroup adds the supplied CoMID group to the target EnvironmentSelector +func (o *EnvironmentSelector) AddGroup(v comid.Group) *EnvironmentSelector { + if o.Groups == nil { + o.Groups = new([]comid.Group) + } + + *o.Groups = append(*o.Groups, v) + + return o +} + +// Valid ensures that the target EnvironmentSelector is correctly populated +func (o EnvironmentSelector) Valid() error { + if o.Classes == nil && o.Groups == nil && o.Instances == nil { + return errors.New("non-empty<> constraint violation") + } + + if o.Classes != nil { + if o.Instances != nil || o.Groups != nil { + return errors.New("only one selector type is allowed") + } + } else if o.Instances != nil { + if o.Groups != nil { + return errors.New("only one selector type is allowed") + } + } + + return nil +} diff --git a/coserv/environmentselector_test.go b/coserv/environmentselector_test.go new file mode 100644 index 00000000..d052767e --- /dev/null +++ b/coserv/environmentselector_test.go @@ -0,0 +1,24 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEnvironmentSelector_Valid_mixed_fail(t *testing.T) { + tv := badExampleMixedSelector(t) + + err := tv.Valid() + assert.EqualError(t, err, "only one selector type is allowed") +} + +func TestEnvironmentSelector_Valid_empty_fail(t *testing.T) { + tv := badExampleEmptySelector(t) + + err := tv.Valid() + assert.EqualError(t, err, "non-empty<> constraint violation") +} diff --git a/coserv/quads.go b/coserv/quads.go new file mode 100644 index 00000000..e8af8096 --- /dev/null +++ b/coserv/quads.go @@ -0,0 +1,16 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import "github.com/veraison/corim/comid" + +type RefValQuad struct { + Authorities *[]comid.CryptoKey `cbor:"1,keyasint"` + RVTriple *comid.ValueTriple `cbor:"2,keyasint"` +} + +type AKQuad struct { + Authorities *[]comid.CryptoKey `cbor:"1,keyasint"` + AKTriple *comid.KeyTriple `cbor:"2,keyasint"` +} diff --git a/coserv/query.go b/coserv/query.go new file mode 100644 index 00000000..42e46468 --- /dev/null +++ b/coserv/query.go @@ -0,0 +1,61 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import ( + "fmt" + "time" +) + +// Query is the internal representation of a Query data item +type Query struct { + ArtifactType ArtifactType `cbor:"0,keyasint"` + EnvironmentSelector EnvironmentSelector `cbor:"1,keyasint"` + Timestamp time.Time `cbor:"2,keyasint"` + ResultType ResultType `cbor:"3,keyasint"` +} + +// NewQuery creates a new Query instance with the timestamp set to instantiation time. +// (If needed, the timestamp can be changed using SetTimestamp.) +// An error is returned if the supplied environment selector is invalid. +func NewQuery( + artifactType ArtifactType, + envSelector EnvironmentSelector, + resultType ResultType, +) (*Query, error) { + if err := envSelector.Valid(); err != nil { + return nil, fmt.Errorf("invalid environment selector: %w", err) + } + + return &Query{ + ArtifactType: artifactType, + EnvironmentSelector: envSelector, + Timestamp: time.Now(), + ResultType: resultType, + }, nil +} + +// SetTimestamp allows setting an explicit timestamp for the target Query object +func (o *Query) SetTimestamp(ts time.Time) *Query { + o.Timestamp = ts + return o +} + +// Valid ensures that the Query target is correctly populated +func (o Query) Valid() error { + // TODO(tho) add tests for these two: + // * artifact and result type mismatch should be caught on decoding + // * ditto for profile syntax errors + + if err := o.EnvironmentSelector.Valid(); err != nil { + return fmt.Errorf("invalid environment selector: %w", err) + } + + zeroTime := time.Time{} + if o.Timestamp.Equal(zeroTime) { + return fmt.Errorf("timestamp not set") + } + + return nil +} diff --git a/coserv/query_test.go b/coserv/query_test.go new file mode 100644 index 00000000..4ef9e5b9 --- /dev/null +++ b/coserv/query_test.go @@ -0,0 +1,34 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQuery_Valid_empty_query(t *testing.T) { + tv := &Query{} + + actual := tv.Valid() + + expected := "invalid environment selector: non-empty<> constraint violation" + + assert.EqualError(t, actual, expected) +} + +func TestQuery_Valid_invalid_timestamp(t *testing.T) { + tv := exampleClassQuery(t) + + require.NotNil(t, tv.SetTimestamp(time.Time{})) + + actual := tv.Valid() + + expected := "timestamp not set" + + assert.EqualError(t, actual, expected) +} diff --git a/coserv/resultset.go b/coserv/resultset.go new file mode 100644 index 00000000..87d9b711 --- /dev/null +++ b/coserv/resultset.go @@ -0,0 +1,74 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import ( + "errors" + "time" + + "github.com/veraison/cmw" +) + +type ResultSet struct { + RVQ *[]RefValQuad `cbor:"0,keyasint,omitempty"` + AKQ *[]AKQuad `cbor:"3,keyasint,omitempty"` + // TODO(tho) add endorsed values + // TODO(tho) add CoTS + Expiry *time.Time `cbor:"10,keyasint"` + SourceArtifacts *[]cmw.CMW `cbor:"11,keyasint,omitempty"` +} + +// NewResultSet instantiates a new ResultSet +func NewResultSet() *ResultSet { + return &ResultSet{} +} + +// AddReferenceValues adds the supplied ref-val quad to the target ResultSet +func (o *ResultSet) AddReferenceValues(v RefValQuad) *ResultSet { + if o.RVQ == nil { + o.RVQ = new([]RefValQuad) + } + + *o.RVQ = append(*o.RVQ, v) + + return o +} + +// AddAttestationKeys adds the supplied ak quad to the target ResultSet +func (o *ResultSet) AddAttestationKeys(v AKQuad) *ResultSet { + if o.AKQ == nil { + o.AKQ = new([]AKQuad) + } + + *o.AKQ = append(*o.AKQ, v) + + return o +} + +// AddSourceArtifacts adds the supplied CMW to the target ResultSet +func (o *ResultSet) AddSourceArtifacts(v cmw.CMW) *ResultSet { // nolint:gocritic + if o.SourceArtifacts == nil { + o.SourceArtifacts = new([]cmw.CMW) + } + + *o.SourceArtifacts = append(*o.SourceArtifacts, v) + + return o +} + +// SetExpiry sets the Expiry attribute of the target ResultSet to the supplied time +func (o *ResultSet) SetExpiry(exp time.Time) *ResultSet { + o.Expiry = &exp + return o +} + +// Valid checks that the supplied ResultSet is syntactically correct +func (o ResultSet) Valid() error { + if o.Expiry == nil { + return errors.New("missing mandatory expiry") + } + // The coherency between query and results must be checked by the Coserv's + // Valid() + return nil +} diff --git a/coserv/resultset_test.go b/coserv/resultset_test.go new file mode 100644 index 00000000..746b6b9f --- /dev/null +++ b/coserv/resultset_test.go @@ -0,0 +1,41 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/cmw" + "github.com/veraison/corim/comid" +) + +func TestResultSet_AddAttestationKeys(t *testing.T) { + authority, err := comid.NewCryptoKeyTaggedBytes(testAuthority) + require.NoError(t, err) + + akq := AKQuad{ + Authorities: &[]comid.CryptoKey{*authority}, + AKTriple: &comid.KeyTriple{ + Environment: comid.Environment{ + Class: comid.NewClassBytes(testBytes), + }, + VerifKeys: comid.CryptoKeys{ + comid.MustNewPKIXBase64Key(comid.TestECPubKey), + }, + }, + } + + rset := NewResultSet().SetExpiry(testExpiry).AddAttestationKeys(akq) + assert.NotNil(t, rset) +} + +func TestResultSet_AddSourceArtifacts(t *testing.T) { + cmw0, err := cmw.NewMonad("application/vnd.example.refvals", []byte{0x00, 0x01, 0x02, 0x03}) + require.NoError(t, err) + + rset := NewResultSet().SetExpiry(testExpiry).AddSourceArtifacts(*cmw0) + assert.NotNil(t, rset) +} diff --git a/coserv/resulttype.go b/coserv/resulttype.go new file mode 100644 index 00000000..8bdb3be6 --- /dev/null +++ b/coserv/resulttype.go @@ -0,0 +1,26 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +type ResultType uint8 + +const ( + ResultTypeCollectedArtifacts ResultType = iota + ResultTypeSourceArtifacts + ResultTypeBoth +) + +// String returns the string representation of the target ResultType +func (a ResultType) String() string { + switch a { + case ResultTypeCollectedArtifacts: + return "collected-artifacts" + case ResultTypeSourceArtifacts: + return "source-artifacts" + case ResultTypeBoth: + return "both" + } + // unreachable + return "" +} diff --git a/coserv/test_common.go b/coserv/test_common.go new file mode 100644 index 00000000..d41daa89 --- /dev/null +++ b/coserv/test_common.go @@ -0,0 +1,158 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coserv + +import ( + "os" + "path" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" + "github.com/veraison/swid" +) + +var ( + testExpiry, _ = time.Parse("2006-01-02T15:04:05Z", "2030-12-13T18:30:02Z") + testTimestamp, _ = time.Parse("2006-01-02T15:04:05Z", "2030-12-01T18:30:01Z") + testAuthority = []byte{0xab, 0xcd, 0xef} + testBytes = []byte{0x00, 0x11, 0x22, 0x33} +) + +func readTestVectorSlice(t *testing.T, fname string) []byte { + b, err := os.ReadFile(path.Join("testvectors", fname)) // nolint:gosec + require.NoError(t, err) + return b +} + +func readTestVectorString(t *testing.T, fname string) string { + b, err := os.ReadFile(path.Join("testvectors", fname)) // nolint:gosec + require.NoError(t, err) + return string(b) +} + +func exampleClassSelector(t *testing.T) *EnvironmentSelector { + class0 := comid.NewClassBytes(testBytes). + SetVendor("Example Vendor"). + SetModel("Example Model") + require.NotNil(t, class0) + + class1 := comid.NewClassUUID(comid.TestUUID) + require.NotNil(t, class1) + + selector := NewEnvironmentSelector(). + AddClass(*class0). + AddClass(*class1) + require.NotNil(t, selector) + + return selector +} + +func exampleClassSelector2(t *testing.T) *EnvironmentSelector { + class0 := comid.NewClassBytes(testBytes). + SetVendor("Example Vendor"). + SetModel("Example Model") + require.NotNil(t, class0) + + selector := NewEnvironmentSelector(). + AddClass(*class0) + require.NotNil(t, selector) + + return selector +} + +func exampleInstanceSelector(t *testing.T) *EnvironmentSelector { + instance0, err := comid.NewUEIDInstance(comid.TestUEID) + require.NoError(t, err) + + instance1, err := comid.NewBytesInstance(comid.TestBytes) + require.NoError(t, err) + + selector := NewEnvironmentSelector(). + AddInstance(*instance0). + AddInstance(*instance1) + require.NotNil(t, selector) + + return selector +} + +func exampleGroupSelector(t *testing.T) *EnvironmentSelector { + group0, err := comid.NewBytesGroup(comid.TestBytes) + require.NoError(t, err) + + group1, err := comid.NewUUIDGroup(comid.TestUUID) + require.NoError(t, err) + + selector := NewEnvironmentSelector(). + AddGroup(*group0). + AddGroup(*group1) + require.NotNil(t, selector) + + return selector +} + +func badExampleMixedSelector(t *testing.T) *EnvironmentSelector { + group0, err := comid.NewBytesGroup(comid.TestBytes) + require.NoError(t, err) + + instance0, err := comid.NewUEIDInstance(comid.TestUEID) + require.NoError(t, err) + + class0 := comid.NewClassUUID(comid.TestUUID) + require.NotNil(t, class0) + + selector := NewEnvironmentSelector(). + AddGroup(*group0). + AddInstance(*instance0). + AddGroup(*group0) + require.NotNil(t, selector) + + return selector +} + +func badExampleEmptySelector(t *testing.T) *EnvironmentSelector { + es := NewEnvironmentSelector() + require.NotNil(t, es) + return es +} + +func exampleClassQuery(t *testing.T) *Query { + qry, err := NewQuery(ArtifactTypeReferenceValues, *exampleClassSelector(t), ResultTypeCollectedArtifacts) + require.NoError(t, err) + return qry +} + +func exampleReferenceValuesResultSet(t *testing.T) *ResultSet { + env := comid.Environment{ + Class: comid.NewClassBytes(comid.TestBytes), + } + + measurement, err := comid.NewUUIDMeasurement(comid.TestUUID) + require.NoError(t, err) + measurement.SetVersion("1.2.3", swid.VersionSchemeSemVer).SetMinSVN(2) + + measurements := comid.NewMeasurements().Add(measurement) + + refval := comid.ValueTriple{ + Environment: env, + Measurements: *measurements, + } + + require.NoError(t, refval.Valid()) + + authority, err := comid.NewCryptoKeyTaggedBytes(testAuthority) + require.NoError(t, err) + + rvq := RefValQuad{ + Authorities: &[]comid.CryptoKey{*authority}, + RVTriple: &refval, + } + + rset := NewResultSet(). + SetExpiry(testExpiry). + AddReferenceValues(rvq) + + return rset +} diff --git a/coserv/testvectors/Makefile b/coserv/testvectors/Makefile new file mode 100644 index 00000000..67e8c722 --- /dev/null +++ b/coserv/testvectors/Makefile @@ -0,0 +1,20 @@ +SRCS := $(wildcard *.diag) +CBOR := $(SRCS:.diag=.cbor) +B64U := $(SRCS:.diag=.b64u) +HEX := $(SRCS:.diag=.hex) + +%.cbor: %.diag ; diag2cbor.rb < $< > $@ + +CLEANFILES += $(CBOR) + +%.b64u: %.cbor ; basenc --base64url --wrap 0 $< | tr -d '=' > $@ + +CLEANFILES += $(B64U) + +%.hex: %.cbor ; xxd -p -c0 $< > $@ + +CLEANFILES += $(HEX) + +all: $(CBOR) $(B64U) $(HEX) + +clean: ; $(RM) -f $(CLEANFILES) diff --git a/coserv/testvectors/example-class-selector-noindent.b64u b/coserv/testvectors/example-class-selector-noindent.b64u new file mode 100644 index 00000000..95a8b905 --- /dev/null +++ b/coserv/testvectors/example-class-selector-noindent.b64u @@ -0,0 +1 @@ +ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIKjANkCMEQAESIzAW5FeGFtcGxlIFZlbmRvcgJtRXhhbXBsZSBNb2RlbKEA2CVQMftavwI-SZKqTpX5wVA7-gLAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwA \ No newline at end of file diff --git a/coserv/testvectors/example-class-selector-noindent.cbor b/coserv/testvectors/example-class-selector-noindent.cbor new file mode 100644 index 0000000000000000000000000000000000000000..52d61eb5d6b3af99d8e66d0b7e1296ab9e2b9717 GIT binary patch literal 137 zcmZ3)P@z_mm~NF?k(gVMld6}TpQ~eJU}S2QoUB`rlUS0LUzDqCsAr&Oz_^5giE$xA z(_)63Oa?9tf=b4Wd9Dy$3Sp^vDfvZAxiG$OeoAW2LWUcv0fxV$_A}XePFm$R_2t9Rt9R-`U1doGB7hREip1JFfmjyI65;h JIxsO>0|6v{A^rdW literal 0 HcmV?d00001 diff --git a/coserv/testvectors/example-group-selector.diag b/coserv/testvectors/example-group-selector.diag new file mode 100644 index 00000000..d2bcb95f --- /dev/null +++ b/coserv/testvectors/example-group-selector.diag @@ -0,0 +1,14 @@ +{ + 0: "tag:example.com,2025:cc-platform#1.0.0", + 1: { + 0: 2, + 1:{ + 2: [ + 560(h'8999786556'), + 37(h'31FB5ABF023E4992AA4E95F9C1503BFA') + ] + }, + 2: 0("2030-12-01T18:30:01Z"), + 3: 1 + } +} diff --git a/coserv/testvectors/example-group-selector.hex b/coserv/testvectors/example-group-selector.hex new file mode 100644 index 00000000..86957d6a --- /dev/null +++ b/coserv/testvectors/example-group-selector.hex @@ -0,0 +1 @@ +a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10282d90230458999786556d8255031fb5abf023e4992aa4e95f9c1503bfa02c074323033302d31322d30315431383a33303a30315a0301 diff --git a/coserv/testvectors/example-instance-selector.b64u b/coserv/testvectors/example-instance-selector.b64u new file mode 100644 index 00000000..00d2526b --- /dev/null +++ b/coserv/testvectors/example-instance-selector.b64u @@ -0,0 +1 @@ +ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAYLZAiZHAt6tvu_erdkCMEWJmXhlVgLAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwI \ No newline at end of file diff --git a/coserv/testvectors/example-instance-selector.cbor b/coserv/testvectors/example-instance-selector.cbor new file mode 100644 index 0000000000000000000000000000000000000000..8671ec2c0ab749068dd3db30f12027f2d9343a7e GIT binary patch literal 95 zcmZ3)P@z_mm~NF?k(gVMld6}TpQ~eJU}S2QoUB`rlUS0LUzDqCsAr&Oz_^5giE$xg z(@iEdccy!5_r1Tj_9l~oYv;_0)G($4B}N9u2D*kux(0?Jh89-F237`!QOryL3Ctdn literal 0 HcmV?d00001 diff --git a/coserv/testvectors/example-instance-selector.diag b/coserv/testvectors/example-instance-selector.diag new file mode 100644 index 00000000..bcccc782 --- /dev/null +++ b/coserv/testvectors/example-instance-selector.diag @@ -0,0 +1,14 @@ +{ + 0: "tag:example.com,2025:cc-platform#1.0.0", + 1: { + 0: 2, + 1: { + 1: [ + 550(h'02DEADBEEFDEAD'), + 560(h'8999786556') + ] + }, + 2: 0("2030-12-01T18:30:01Z"), + 3: 2 + } +} diff --git a/coserv/testvectors/example-instance-selector.hex b/coserv/testvectors/example-instance-selector.hex new file mode 100644 index 00000000..ce57b3e5 --- /dev/null +++ b/coserv/testvectors/example-instance-selector.hex @@ -0,0 +1 @@ +a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10182d902264702deadbeefdeadd9023045899978655602c074323033302d31322d30315431383a33303a30315a0302 diff --git a/coserv/testvectors/rv-class-simple-results-source-artifacts.b64u b/coserv/testvectors/rv-class-simple-results-source-artifacts.b64u new file mode 100644 index 00000000..e4ff9ee7 --- /dev/null +++ b/coserv/testvectors/rv-class-simple-results-source-artifacts.b64u @@ -0,0 +1 @@ +owB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGjANkCMEQAESIzAW5FeGFtcGxlIFZlbmRvcgJtRXhhbXBsZSBNb2RlbALAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwECowCACsB0MjAzMC0xMi0xM1QxODozMDowMloLgoJ4H2FwcGxpY2F0aW9uL3ZuZC5leGFtcGxlLnJlZnZhbHNEr66trIJ4H2FwcGxpY2F0aW9uL3ZuZC5leGFtcGxlLnJlZnZhbHNErayrqg \ No newline at end of file diff --git a/coserv/testvectors/rv-class-simple-results-source-artifacts.cbor b/coserv/testvectors/rv-class-simple-results-source-artifacts.cbor new file mode 100644 index 0000000000000000000000000000000000000000..8e29f1e9d3e0ceb42385bfd012dea9425eb709fb GIT binary patch literal 223 zcmZ3?P@z_mm~NF?k(gVMld6}TpQ~eJU}S2QoUB`rlUS0LUzDqCsAr&Oz_^5giE$xA z<6?%JOa?9tf=b4Wd9Dy$3Sp^vDfvZAxiG$OeoAT%(}5Br17ibSLnB=S!w^FYD`NvI z1H&k0MyACK4O|FGLt~hvQ51JmQ-yqDK|xMta$-qlex80=UWy*fZAGbRWr;b(F6-B= NT|IvV1eQ4il(`E4zpzi0 literal 0 HcmV?d00001 diff --git a/coserv/testvectors/rv-class-simple-results.diag b/coserv/testvectors/rv-class-simple-results.diag new file mode 100644 index 00000000..297e35b4 --- /dev/null +++ b/coserv/testvectors/rv-class-simple-results.diag @@ -0,0 +1,54 @@ +{ + / profile / 0: "tag:example.com,2025:cc-platform#1.0.0", + / query / 1: { + / artifact-type / 0: 2, / reference-values / + / environment-selector / 1: { + / class / 0: [ + { + / class-id / 0: 560(h'00112233'), / tagged-bytes / + / vendor / 1: "Example Vendor", + / model / 2: "Example Model" + } + ] + }, + / timestamp / 2: 0("2030-12-01T18:30:01Z"), + / result-type / 3: 0 / collected-material / + }, + / results / 2: { + / expiry / 10: 0("2030-12-13T18:30:02Z"), + / rvq / 0: [ + { + / authorities / 1: [ 560(h'abcdef') ], + / reference-triple / 2: [ + / environment-map / { + / class / 0: { + / class-id / 0: 560(h'00112233'), + / vendor / 1: "Example Vendor", + / model / 2: "Example Model" + } + }, + [ + / measurement-map / { + / mval / 1: { + / name / 11: "Component A", + / digests / 2: [ + [ 1, h'aa' ], + [ 2, h'bb' ] + ] + } + }, + / measurement-map / { + / mval / 1: { + / name / 11: "Component B", + / digests / 2: [ + [ 1, h'cc' ], + [ 2, h'dd' ] + ] + } + } + ] + ] + } + ] + } +} diff --git a/coserv/testvectors/rv-class-simple-results.hex b/coserv/testvectors/rv-class-simple-results.hex new file mode 100644 index 00000000..75d2e24f --- /dev/null +++ b/coserv/testvectors/rv-class-simple-results.hex @@ -0,0 +1 @@ +a30078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10081a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c02c074323033302d31322d30315431383a33303a30315a030002a20ac074323033302d31322d31335431383a33303a30325a0081a20181d9023043abcdef0282a100a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c82a101a20b6b436f6d706f6e656e7420410282820141aa820241bba101a20b6b436f6d706f6e656e7420420282820141cc820241dd diff --git a/coserv/testvectors/rv-class-simple.b64u b/coserv/testvectors/rv-class-simple.b64u new file mode 100644 index 00000000..c561f23e --- /dev/null +++ b/coserv/testvectors/rv-class-simple.b64u @@ -0,0 +1 @@ +ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGjANkCMEQAESIzAW5FeGFtcGxlIFZlbmRvcgJtRXhhbXBsZSBNb2RlbALAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwE \ No newline at end of file diff --git a/coserv/testvectors/rv-class-simple.cbor b/coserv/testvectors/rv-class-simple.cbor new file mode 100644 index 0000000000000000000000000000000000000000..2c8c7e40de06141b6f379ae09d1be631bf83a445 GIT binary patch literal 116 zcmZ3)P@z_mm~NF?k(gVMld6}TpQ~eJU}S2QoUB`rlUS0LUzDqCsAr&Oz_^5giE$xA z<6?%JOa?9tf=b4Wd9Dy$3Sp^vDfvZAxiG$OeoAT%(}5Br17ibSLnB=S!w^FYD`NvI J1H&k0MgU57B1!-N literal 0 HcmV?d00001 diff --git a/coserv/testvectors/rv-class-simple.diag b/coserv/testvectors/rv-class-simple.diag new file mode 100644 index 00000000..1df73258 --- /dev/null +++ b/coserv/testvectors/rv-class-simple.diag @@ -0,0 +1,17 @@ +{ + / profile / 0: "tag:example.com,2025:cc-platform#1.0.0", + / query / 1: { + / artifact-type / 0: 2, / reference-values / + / environment-selector / 1: { + / class / 0: [ + { + / class-id / 0: 560(h'00112233'), / tagged-bytes / + / vendor / 1: "Example Vendor", + / model / 2: "Example Model" + } + ] + }, + / timestamp / 2: 0("2030-12-01T18:30:01Z"), + / result-type / 3: 1 / source-material / + } +} diff --git a/coserv/testvectors/rv-class-simple.hex b/coserv/testvectors/rv-class-simple.hex new file mode 100644 index 00000000..2dd92ca0 --- /dev/null +++ b/coserv/testvectors/rv-class-simple.hex @@ -0,0 +1 @@ +a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10081a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c02c074323033302d31322d30315431383a33303a30315a0301 diff --git a/coserv/testvectors/rv-results.b64u b/coserv/testvectors/rv-results.b64u new file mode 100644 index 00000000..2187a3a0 --- /dev/null +++ b/coserv/testvectors/rv-results.b64u @@ -0,0 +1 @@ +owB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGhANkCMEWJmXhlVgLAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwACogCBogGB2QIwQ6vN7wKCoQChANkCMEWJmXhlVoGiANglUDH7Wr8CPkmSqk6V-cFQO_oBogCiAGUxLjIuMwEZQAAB2QIpAgrAdDIwMzAtMTItMTNUMTg6MzA6MDJa \ No newline at end of file diff --git a/coserv/testvectors/rv-results.cbor b/coserv/testvectors/rv-results.cbor new file mode 100644 index 0000000000000000000000000000000000000000..2360747958a82d00fd06e5b3adcda38dc140e81f GIT binary patch literal 180 zcmZ3?P@z_mm~NF?k(gVMld6}TpQ~eJU}S2QoUB`rlUS0LUzDqCsAr&Oz_^5giE$xA z<3fg;Oa`uWVm=2T}85kSr8XD;u7={>HSQ#5w85l+}GcYY;Xk5hD2vq32`s{nA zriBbhx*8WT+)xcL{2jHQ$ Date: Wed, 9 Jul 2025 13:00:18 +0100 Subject: [PATCH 108/110] Fix Incorrect Encoding of CES Triples (#208) * Fix Incorrect Encoding of CES Triples Fixes #207 Signed-off-by: Yogesh Deshpande --- comid/cond_endorse_series_triple.go | 16 ++++ comid/example_test.go | 124 ++++++++++++++-------------- 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/comid/cond_endorse_series_triple.go b/comid/cond_endorse_series_triple.go index bff08712..5474c869 100644 --- a/comid/cond_endorse_series_triple.go +++ b/comid/cond_endorse_series_triple.go @@ -17,6 +17,7 @@ type StatefulEnv = ValueTriple // First successful match terminates matching and corresponding addition are added // as Endorsements type CondEndorseSeriesRecord struct { + _ struct{} `cbor:",toarray"` Selection Measurements `json:"selection"` Addition Measurements `json:"addition"` } @@ -76,6 +77,21 @@ func (o *CondEndorseSeriesRecords) Valid() error { return (*extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).Valid() } +func (o CondEndorseSeriesRecords) MarshalCBOR() ([]byte, error) { + return (extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).MarshalCBOR() +} + +func (o *CondEndorseSeriesRecords) UnmarshalCBOR(data []byte) error { + return (*extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).UnmarshalCBOR(data) +} +func (o CondEndorseSeriesRecords) MarshalJSON() ([]byte, error) { + return (extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).MarshalJSON() +} + +func (o *CondEndorseSeriesRecords) UnmarshalJSON(data []byte) error { + return (*extensions.Collection[CondEndorseSeriesRecord, *CondEndorseSeriesRecord])(o).UnmarshalJSON(data) +} + // The Conditional Endorsement Series Triple is used to assert endorsed values based // on an initial condition match (specified by Condition StatefulEnv) followed by a series // condition match (specified in selection: inside conditional-series-record). diff --git a/comid/example_test.go b/comid/example_test.go index 30e050eb..dfc73cc4 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -161,8 +161,8 @@ func Example_encode() { } // Output: - // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a5008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d08818282a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfaa16656616c75657381a2686164646974696f6e81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a3064802005e1000000001075020010db8000000000000000000000068094702deadbeefdead6973656c656374696f6e81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a501d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff - // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"conditional-endorsement-series":[{"statefulenv":{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]},"series":{"Values":[{"selection":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w=="}}],"addition":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","ueid":"At6tvu/erQ=="}}]}]}}]}} + // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a5008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d08818282a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa818281a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a501d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a3064802005e1000000001075020010db8000000000000000000000068094702deadbeefdead + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"conditional-endorsement-series":[{"statefulenv":{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]},"series":[{"selection":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w=="}}],"addition":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","ueid":"At6tvu/erQ=="}}]}]}]}} } func Example_encode_PSA() { @@ -303,7 +303,7 @@ func Example_decode_JSON() { { "value": { "digests": [ - "sha-256:3q2+7w==" + "sha-256:5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ] } } @@ -332,7 +332,7 @@ func Example_decode_JSON() { }, "value": { "digests": [ - "sha-256:3q2+7w==" + "sha-256:5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" ], "svn": { "type": "exact-value", @@ -480,70 +480,74 @@ func Example_decode_JSON() { } ] }, - "series": { - "Values": [ - { - "selection": [ - { - "key": { - "type": "uuid", - "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + "series": [ + { + "selection": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "svn": { + "type": "exact-value", + "value": 2 }, - "value": { - "svn": { - "type": "exact-value", - "value": 2 - }, - "version": {"value": "2.0.0", "scheme": "semver" } + "version": { + "value": "2.0.0", + "scheme": "semver" } } - ], - "addition": [ - { - "key": { - "type": "uuid", - "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" - }, - "value": { - "mac-addr": "02:00:5e:10:00:00:00:01", - "ip-addr": "2001:db8::68", - "ueid": "At6tvu/erQ==" - } + } + ], + "addition": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "mac-addr": "02:00:5e:10:00:00:00:01", + "ip-addr": "2001:db8::68", + "ueid": "At6tvu/erQ==" } - ] - }, - { - "selection": [ - { - "key": { - "type": "uuid", - "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + } + ] + }, + { + "selection": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "svn": { + "type": "exact-value", + "value": 3 }, - "value": { - "svn": { - "type": "exact-value", - "value": 3 - }, - "version": {"value": "3.0.1", "scheme": "semver" } + "version": { + "value": "3.0.1", + "scheme": "semver" } } - ], - "addition": [ - { - "key": { - "type": "uuid", - "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" - }, - "value": { - "mac-addr": "02:00:5e:10:00:00:00:02", - "ip-addr": "2001:db8::69", - "ueid": "At6tvu/erQ==" - } + } + ], + "addition": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "mac-addr": "02:00:5e:10:00:00:00:02", + "ip-addr": "2001:db8::69", + "ueid": "At6tvu/erQ==" } - ] - } - ] - } + } + ] + } + ] } ] } From 312d3b5f20e072fd66538d88a54d6de2b22c4612 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Thu, 10 Jul 2025 17:49:10 +0200 Subject: [PATCH 109/110] feat(coserv): implement stateful selectors Signed-off-by: Thomas Fossati --- README.md | 3 +- coserv/coserv_test.go | 18 ++- coserv/environmentselector.go | 147 ++++++++++++++++-- coserv/environmentselector_test.go | 30 ++++ coserv/test_common.go | 20 +-- .../example-class-selector-noindent.b64u | 2 +- .../example-class-selector-noindent.cbor | Bin 137 -> 139 bytes .../example-class-selector-noindent.diag | 2 +- .../example-class-selector-noindent.hex | 2 +- .../testvectors/example-class-selector.b64u | 2 +- .../testvectors/example-class-selector.cbor | Bin 137 -> 139 bytes .../testvectors/example-class-selector.diag | 8 +- coserv/testvectors/example-class-selector.hex | 2 +- .../testvectors/example-group-selector.b64u | 2 +- .../testvectors/example-group-selector.cbor | Bin 103 -> 105 bytes .../testvectors/example-group-selector.diag | 4 +- coserv/testvectors/example-group-selector.hex | 2 +- .../example-instance-selector.b64u | 2 +- .../example-instance-selector.cbor | Bin 95 -> 97 bytes .../example-instance-selector.diag | 4 +- .../testvectors/example-instance-selector.hex | 2 +- ...class-simple-results-source-artifacts.b64u | 2 +- ...class-simple-results-source-artifacts.cbor | Bin 223 -> 224 bytes ...class-simple-results-source-artifacts.diag | 4 +- ...-class-simple-results-source-artifacts.hex | 2 +- .../testvectors/rv-class-simple-results.b64u | 2 +- .../testvectors/rv-class-simple-results.cbor | Bin 251 -> 252 bytes .../testvectors/rv-class-simple-results.diag | 8 +- .../testvectors/rv-class-simple-results.hex | 2 +- coserv/testvectors/rv-class-simple.b64u | 2 +- coserv/testvectors/rv-class-simple.cbor | Bin 116 -> 117 bytes coserv/testvectors/rv-class-simple.diag | 4 +- coserv/testvectors/rv-class-simple.hex | 2 +- coserv/testvectors/rv-class-stateful.b64u | 1 + coserv/testvectors/rv-class-stateful.cbor | Bin 0 -> 140 bytes coserv/testvectors/rv-class-stateful.diag | 29 ++++ coserv/testvectors/rv-class-stateful.hex | 1 + coserv/testvectors/rv-results.b64u | 2 +- coserv/testvectors/rv-results.cbor | Bin 180 -> 181 bytes coserv/testvectors/rv-results.diag | 4 +- coserv/testvectors/rv-results.hex | 2 +- go.mod | 4 +- go.sum | 6 +- 43 files changed, 266 insertions(+), 63 deletions(-) create mode 100644 coserv/testvectors/rv-class-stateful.b64u create mode 100644 coserv/testvectors/rv-class-stateful.cbor create mode 100644 coserv/testvectors/rv-class-stateful.diag create mode 100644 coserv/testvectors/rv-class-stateful.hex diff --git a/README.md b/README.md index d118c42f..1ec0f167 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ [![linters](https://github.com/veraison/corim/actions/workflows/linters.yml/badge.svg)](https://github.com/veraison/corim/actions/workflows/linters.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/veraison/corim.svg)](https://pkg.go.dev/github.com/veraison/corim) -The [`corim/corim`](corim) and [`corim/comid`](comid) packages provide a golang API for low-level manipulation of [Concise Reference Integrity Manifest (CoRIM)](https://datatracker.ietf.org/doc/draft-birkholz-rats-corim/) and Concise Module Identifier (CoMID) tags respectively. +The [`corim/corim`](corim) and [`corim/comid`](comid) packages provide a golang API for low-level manipulation of [Concise Reference Integrity Manifest (CoRIM)](https://datatracker.ietf.org/doc/draft-ietf-rats-corim/) and Concise Module Identifier (CoMID) tags respectively. +The [`corim/coserv`](coserv) package provides a golang API for working with [Concise Selector for Endorsements and Reference Values](https://datatracker.ietf.org/doc/draft-howard-rats-coserv). > [!NOTE] > These API are still in active development (as is the underlying CoRIM spec). diff --git a/coserv/coserv_test.go b/coserv/coserv_test.go index 8c99f261..6b938509 100644 --- a/coserv/coserv_test.go +++ b/coserv/coserv_test.go @@ -20,7 +20,7 @@ func TestCoserv_ToCBOR_rv_class_simple(t *testing.T) { require.NotNil(t, class) envSelector := NewEnvironmentSelector(). - AddClass(*class) + AddClass(StatefulClass{Class: class}) require.NotNil(t, envSelector) query, err := NewQuery(ArtifactTypeReferenceValues, *envSelector, ResultTypeSourceArtifacts) @@ -265,6 +265,20 @@ func TestCoserv_ToEDN_ok(t *testing.T) { assert.Equal(t, expected, actual) } +func TestCoserv_FromCBOR_Stateful(t *testing.T) { + tv := readTestVectorSlice(t, "rv-class-stateful.cbor") + + var actual Coserv + + err := actual.FromCBOR(tv) + require.NoError(t, err) + + // here we only care about the measurements + + assert.Len(t, *actual.Query.EnvironmentSelector.Classes, 1) + assert.NotNil(t, (*actual.Query.EnvironmentSelector.Classes)[0].Measurements) +} + func TestCoserv_FromCBOR_Results(t *testing.T) { tv := readTestVectorSlice(t, "rv-class-simple-results.cbor") @@ -355,7 +369,7 @@ func TestCoserv_results_ToCBOR_ok(t *testing.T) { require.NotNil(t, class) envSelector := NewEnvironmentSelector(). - AddClass(*class) + AddClass(StatefulClass{Class: class}) require.NotNil(t, envSelector) query, err := NewQuery(ArtifactTypeReferenceValues, *envSelector, ResultTypeCollectedArtifacts) diff --git a/coserv/environmentselector.go b/coserv/environmentselector.go index e25436fc..17c21e8e 100644 --- a/coserv/environmentselector.go +++ b/coserv/environmentselector.go @@ -5,14 +5,143 @@ package coserv import ( "errors" + "fmt" + "github.com/fxamacker/cbor/v2" "github.com/veraison/corim/comid" ) +func unmarshalStatefulFirstPass(data []byte) ([]cbor.RawMessage, error) { + var a []cbor.RawMessage + + if err := cbor.Unmarshal(data, &a); err != nil { + return nil, fmt.Errorf("CBOR decoding: %w", err) + } + + alen := len(a) + + if alen < 1 || alen > 2 { + return nil, fmt.Errorf("wrong number of entries (%d) in the array", alen) + } + + return a, nil +} + +type StatefulClass struct { + Class *comid.Class + Measurements *comid.Measurements +} + +func (o StatefulClass) MarshalCBOR() ([]byte, error) { + if o.Class == nil { + return nil, errors.New("mandatory field class not set") + } + + a := []any{o.Class} + if o.Measurements != nil { + a = append(a, o.Measurements) + } + + return cbor.Marshal(a) +} + +func (o *StatefulClass) UnmarshalCBOR(data []byte) error { + a, err := unmarshalStatefulFirstPass(data) + if err != nil { + return fmt.Errorf("unmarshaling StatefulClass: %w", err) + } + + if err := cbor.Unmarshal(a[0], &o.Class); err != nil { + return fmt.Errorf("unmarshaling StatefulClass Class: %w", err) + } + + if len(a) == 2 { + if err := cbor.Unmarshal(a[1], &o.Measurements); err != nil { + return fmt.Errorf("unmarshaling StatefulClass Measurements: %w", err) + } + } + + return nil +} + +type StatefulInstance struct { + Instance *comid.Instance + Measurements *comid.Measurements +} + +func (o StatefulInstance) MarshalCBOR() ([]byte, error) { + if o.Instance == nil { + return nil, errors.New("mandatory field instance not set") + } + + a := []any{o.Instance} + if o.Measurements != nil { + a = append(a, o.Measurements) + } + + return cbor.Marshal(a) +} + +func (o *StatefulInstance) UnmarshalCBOR(data []byte) error { + a, err := unmarshalStatefulFirstPass(data) + if err != nil { + return fmt.Errorf("unmarshaling StatefulInstance: %w", err) + } + + if err := cbor.Unmarshal(a[0], &o.Instance); err != nil { + return fmt.Errorf("unmarshaling StatefulInstance Instance: %w", err) + } + + if len(a) == 2 { + if err := cbor.Unmarshal(a[1], &o.Measurements); err != nil { + return fmt.Errorf("unmarshaling StatefulInstance Measurements: %w", err) + } + } + + return nil +} + +type StatefulGroup struct { + Group *comid.Group + Measurements *comid.Measurements +} + +func (o StatefulGroup) MarshalCBOR() ([]byte, error) { + if o.Group == nil { + return nil, errors.New("mandatory field group not set") + } + + a := []any{o.Group} + if o.Measurements != nil { + a = append(a, o.Measurements) + } + + return cbor.Marshal(a) +} + +func (o *StatefulGroup) UnmarshalCBOR(data []byte) error { + a, err := unmarshalStatefulFirstPass(data) + if err != nil { + return fmt.Errorf("unmarshaling StatefulGroup: %w", err) + } + + if err := cbor.Unmarshal(a[0], &o.Group); err != nil { + return fmt.Errorf("unmarshaling StatefulGroup Group: %w", err) + } + + if len(a) == 2 { + if err := cbor.Unmarshal(a[1], &o.Measurements); err != nil { + return fmt.Errorf("unmarshaling StatefulGroup Measurements: %w", err) + } + } + + return nil +} + type EnvironmentSelector struct { - Classes *[]comid.Class `cbor:"0,keyasint,omitempty"` - Instances *[]comid.Instance `cbor:"1,keyasint,omitempty"` - Groups *[]comid.Group `cbor:"2,keyasint,omitempty"` + Classes *[]StatefulClass `cbor:"0,keyasint,omitempty"` + Instances *[]StatefulInstance `cbor:"1,keyasint,omitempty"` + Groups *[]StatefulGroup `cbor:"2,keyasint,omitempty"` } // NewEnvironmentSelector creates a new EnvironmentSelector instance @@ -21,9 +150,9 @@ func NewEnvironmentSelector() *EnvironmentSelector { } // AddClass adds the supplied CoMID class to the target EnvironmentSelector -func (o *EnvironmentSelector) AddClass(v comid.Class) *EnvironmentSelector { +func (o *EnvironmentSelector) AddClass(v StatefulClass) *EnvironmentSelector { if o.Classes == nil { - o.Classes = new([]comid.Class) + o.Classes = new([]StatefulClass) } *o.Classes = append(*o.Classes, v) @@ -32,9 +161,9 @@ func (o *EnvironmentSelector) AddClass(v comid.Class) *EnvironmentSelector { } // AddInstance adds the supplied CoMID instance to the target EnvironmentSelector -func (o *EnvironmentSelector) AddInstance(v comid.Instance) *EnvironmentSelector { +func (o *EnvironmentSelector) AddInstance(v StatefulInstance) *EnvironmentSelector { if o.Instances == nil { - o.Instances = new([]comid.Instance) + o.Instances = new([]StatefulInstance) } *o.Instances = append(*o.Instances, v) @@ -43,9 +172,9 @@ func (o *EnvironmentSelector) AddInstance(v comid.Instance) *EnvironmentSelector } // AddGroup adds the supplied CoMID group to the target EnvironmentSelector -func (o *EnvironmentSelector) AddGroup(v comid.Group) *EnvironmentSelector { +func (o *EnvironmentSelector) AddGroup(v StatefulGroup) *EnvironmentSelector { if o.Groups == nil { - o.Groups = new([]comid.Group) + o.Groups = new([]StatefulGroup) } *o.Groups = append(*o.Groups, v) diff --git a/coserv/environmentselector_test.go b/coserv/environmentselector_test.go index d052767e..3d8a140b 100644 --- a/coserv/environmentselector_test.go +++ b/coserv/environmentselector_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/veraison/corim/comid" ) func TestEnvironmentSelector_Valid_mixed_fail(t *testing.T) { @@ -22,3 +23,32 @@ func TestEnvironmentSelector_Valid_empty_fail(t *testing.T) { err := tv.Valid() assert.EqualError(t, err, "non-empty<> constraint violation") } + +func TestStatefulClass_MarshalCBOR_invalid_missing_mandatory(t *testing.T) { + tv := StatefulClass{} + _, err := tv.MarshalCBOR() + assert.EqualError(t, err, "mandatory field class not set") +} + +func TestStatefulClass_MarshalCBOR_valid_full(t *testing.T) { + tv := StatefulClass{ + Class: comid.NewClassUUID(comid.TestUUID), + Measurements: comid.NewMeasurements().Add(comid.MustNewUintMeasurement(uint(1))), + } + _, err := tv.MarshalCBOR() + assert.NoError(t, err) +} + +func TestStatefulClass_UnmarshalCBOR_invalid_eof(t *testing.T) { + tv := comid.MustHexDecode(t, "") + var actual StatefulClass + err := actual.UnmarshalCBOR(tv) + assert.EqualError(t, err, "unmarshaling StatefulClass: CBOR decoding: EOF") +} + +func TestStatefulClass_UnmarshalCBOR_invalid_empty_array(t *testing.T) { + tv := comid.MustHexDecode(t, "80") + var actual StatefulClass + err := actual.UnmarshalCBOR(tv) + assert.EqualError(t, err, "unmarshaling StatefulClass: wrong number of entries (0) in the array") +} diff --git a/coserv/test_common.go b/coserv/test_common.go index d41daa89..1a283125 100644 --- a/coserv/test_common.go +++ b/coserv/test_common.go @@ -43,8 +43,8 @@ func exampleClassSelector(t *testing.T) *EnvironmentSelector { require.NotNil(t, class1) selector := NewEnvironmentSelector(). - AddClass(*class0). - AddClass(*class1) + AddClass(StatefulClass{Class: class0}). + AddClass(StatefulClass{Class: class1}) require.NotNil(t, selector) return selector @@ -57,7 +57,7 @@ func exampleClassSelector2(t *testing.T) *EnvironmentSelector { require.NotNil(t, class0) selector := NewEnvironmentSelector(). - AddClass(*class0) + AddClass(StatefulClass{Class: class0}) require.NotNil(t, selector) return selector @@ -71,8 +71,8 @@ func exampleInstanceSelector(t *testing.T) *EnvironmentSelector { require.NoError(t, err) selector := NewEnvironmentSelector(). - AddInstance(*instance0). - AddInstance(*instance1) + AddInstance(StatefulInstance{Instance: instance0}). + AddInstance(StatefulInstance{Instance: instance1}) require.NotNil(t, selector) return selector @@ -86,8 +86,8 @@ func exampleGroupSelector(t *testing.T) *EnvironmentSelector { require.NoError(t, err) selector := NewEnvironmentSelector(). - AddGroup(*group0). - AddGroup(*group1) + AddGroup(StatefulGroup{Group: group0}). + AddGroup(StatefulGroup{Group: group1}) require.NotNil(t, selector) return selector @@ -104,9 +104,9 @@ func badExampleMixedSelector(t *testing.T) *EnvironmentSelector { require.NotNil(t, class0) selector := NewEnvironmentSelector(). - AddGroup(*group0). - AddInstance(*instance0). - AddGroup(*group0) + AddGroup(StatefulGroup{Group: group0}). + AddInstance(StatefulInstance{Instance: instance0}). + AddGroup(StatefulGroup{Group: group0}) require.NotNil(t, selector) return selector diff --git a/coserv/testvectors/example-class-selector-noindent.b64u b/coserv/testvectors/example-class-selector-noindent.b64u index 95a8b905..6818918a 100644 --- a/coserv/testvectors/example-class-selector-noindent.b64u +++ b/coserv/testvectors/example-class-selector-noindent.b64u @@ -1 +1 @@ -ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIKjANkCMEQAESIzAW5FeGFtcGxlIFZlbmRvcgJtRXhhbXBsZSBNb2RlbKEA2CVQMftavwI-SZKqTpX5wVA7-gLAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwA \ No newline at end of file +ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIKBowDZAjBEABEiMwFuRXhhbXBsZSBWZW5kb3ICbUV4YW1wbGUgTW9kZWyBoQDYJVAx-1q_Aj5JkqpOlfnBUDv6AsB0MjAzMC0xMi0wMVQxODozMDowMVoDAA \ No newline at end of file diff --git a/coserv/testvectors/example-class-selector-noindent.cbor b/coserv/testvectors/example-class-selector-noindent.cbor index 52d61eb5d6b3af99d8e66d0b7e1296ab9e2b9717..d60017685d40e85fabe3b7fd32fb4f2d7a9e804f 100644 GIT binary patch delta 16 WcmeBV>}H%`#Mn5|NE1jz>j3~MO9bNp delta 12 TcmeBX>|~r^G|^adVyqqj8Gr-g diff --git a/coserv/testvectors/example-class-selector-noindent.diag b/coserv/testvectors/example-class-selector-noindent.diag index 39d7642d..e6e964f4 100644 --- a/coserv/testvectors/example-class-selector-noindent.diag +++ b/coserv/testvectors/example-class-selector-noindent.diag @@ -1 +1 @@ -{0: "tag:example.com,2025:cc-platform#1.0.0", 1: {0: 2, 1: {0: [{0: 560(h'00112233'), 1: "Example Vendor", 2: "Example Model"}, {0: 37(h'31fb5abf023e4992aa4e95f9c1503bfa')}]}, 2: 0("2030-12-01T18:30:01Z"), 3: 0}} \ No newline at end of file +{0: "tag:example.com,2025:cc-platform#1.0.0", 1: {0: 2, 1: {0: [[{0: 560(h'00112233'), 1: "Example Vendor", 2: "Example Model"}], [{0: 37(h'31fb5abf023e4992aa4e95f9c1503bfa')}]]}, 2: 0("2030-12-01T18:30:01Z"), 3: 0}} \ No newline at end of file diff --git a/coserv/testvectors/example-class-selector-noindent.hex b/coserv/testvectors/example-class-selector-noindent.hex index 03d7bdf4..85d0e4b2 100644 --- a/coserv/testvectors/example-class-selector-noindent.hex +++ b/coserv/testvectors/example-class-selector-noindent.hex @@ -1 +1 @@ -a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10082a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656ca100d8255031fb5abf023e4992aa4e95f9c1503bfa02c074323033302d31322d30315431383a33303a30315a0300 +a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a1008281a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c81a100d8255031fb5abf023e4992aa4e95f9c1503bfa02c074323033302d31322d30315431383a33303a30315a0300 diff --git a/coserv/testvectors/example-class-selector.b64u b/coserv/testvectors/example-class-selector.b64u index 95a8b905..6818918a 100644 --- a/coserv/testvectors/example-class-selector.b64u +++ b/coserv/testvectors/example-class-selector.b64u @@ -1 +1 @@ -ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIKjANkCMEQAESIzAW5FeGFtcGxlIFZlbmRvcgJtRXhhbXBsZSBNb2RlbKEA2CVQMftavwI-SZKqTpX5wVA7-gLAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwA \ No newline at end of file +ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIKBowDZAjBEABEiMwFuRXhhbXBsZSBWZW5kb3ICbUV4YW1wbGUgTW9kZWyBoQDYJVAx-1q_Aj5JkqpOlfnBUDv6AsB0MjAzMC0xMi0wMVQxODozMDowMVoDAA \ No newline at end of file diff --git a/coserv/testvectors/example-class-selector.cbor b/coserv/testvectors/example-class-selector.cbor index 52d61eb5d6b3af99d8e66d0b7e1296ab9e2b9717..d60017685d40e85fabe3b7fd32fb4f2d7a9e804f 100644 GIT binary patch delta 16 WcmeBV>}H%`#Mn5|NE1jz>j3~MO9bNp delta 12 TcmeBX>|~r^G|^adVyqqj8Gr-g diff --git a/coserv/testvectors/example-class-selector.diag b/coserv/testvectors/example-class-selector.diag index a102bcd7..9d2ce120 100644 --- a/coserv/testvectors/example-class-selector.diag +++ b/coserv/testvectors/example-class-selector.diag @@ -4,14 +4,14 @@ 0: 2, 1: { 0: [ - { + [ { 0: 560(h'00112233'), 1: "Example Vendor", 2: "Example Model" - }, - { + } ], + [ { 0: 37(h'31FB5ABF023E4992AA4E95F9C1503BFA') - } + } ] ] }, 2: 0("2030-12-01T18:30:01Z"), diff --git a/coserv/testvectors/example-class-selector.hex b/coserv/testvectors/example-class-selector.hex index 03d7bdf4..85d0e4b2 100644 --- a/coserv/testvectors/example-class-selector.hex +++ b/coserv/testvectors/example-class-selector.hex @@ -1 +1 @@ -a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10082a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656ca100d8255031fb5abf023e4992aa4e95f9c1503bfa02c074323033302d31322d30315431383a33303a30315a0300 +a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a1008281a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c81a100d8255031fb5abf023e4992aa4e95f9c1503bfa02c074323033302d31322d30315431383a33303a30315a0300 diff --git a/coserv/testvectors/example-group-selector.b64u b/coserv/testvectors/example-group-selector.b64u index b2915faf..77c243a4 100644 --- a/coserv/testvectors/example-group-selector.b64u +++ b/coserv/testvectors/example-group-selector.b64u @@ -1 +1 @@ -ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAoLZAjBFiZl4ZVbYJVAx-1q_Aj5JkqpOlfnBUDv6AsB0MjAzMC0xMi0wMVQxODozMDowMVoDAQ \ No newline at end of file +ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAoKB2QIwRYmZeGVWgdglUDH7Wr8CPkmSqk6V-cFQO_oCwHQyMDMwLTEyLTAxVDE4OjMwOjAxWgMB \ No newline at end of file diff --git a/coserv/testvectors/example-group-selector.cbor b/coserv/testvectors/example-group-selector.cbor index a776dba5a1dd9fbd42230a4df92e6b418358296d..f33511f1a98adce5a47115761035c03da4a0bc3f 100644 GIT binary patch delta 19 acmYe#oM6P=c$3M%wR2`gYFOh$YaIYcq6a+y delta 17 Ycmc~ypJ2p!lgYrfb7nE7CX@9(XhXs-kSO^FDN diff --git a/coserv/testvectors/example-instance-selector.diag b/coserv/testvectors/example-instance-selector.diag index bcccc782..8f666a9c 100644 --- a/coserv/testvectors/example-instance-selector.diag +++ b/coserv/testvectors/example-instance-selector.diag @@ -4,8 +4,8 @@ 0: 2, 1: { 1: [ - 550(h'02DEADBEEFDEAD'), - 560(h'8999786556') + [ 550(h'02DEADBEEFDEAD') ], + [ 560(h'8999786556') ] ] }, 2: 0("2030-12-01T18:30:01Z"), diff --git a/coserv/testvectors/example-instance-selector.hex b/coserv/testvectors/example-instance-selector.hex index ce57b3e5..32df8a29 100644 --- a/coserv/testvectors/example-instance-selector.hex +++ b/coserv/testvectors/example-instance-selector.hex @@ -1 +1 @@ -a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10182d902264702deadbeefdeadd9023045899978655602c074323033302d31322d30315431383a33303a30315a0302 +a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a1018281d902264702deadbeefdead81d9023045899978655602c074323033302d31322d30315431383a33303a30315a0302 diff --git a/coserv/testvectors/rv-class-simple-results-source-artifacts.b64u b/coserv/testvectors/rv-class-simple-results-source-artifacts.b64u index e4ff9ee7..ebfc463c 100644 --- a/coserv/testvectors/rv-class-simple-results-source-artifacts.b64u +++ b/coserv/testvectors/rv-class-simple-results-source-artifacts.b64u @@ -1 +1 @@ -owB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGjANkCMEQAESIzAW5FeGFtcGxlIFZlbmRvcgJtRXhhbXBsZSBNb2RlbALAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwECowCACsB0MjAzMC0xMi0xM1QxODozMDowMloLgoJ4H2FwcGxpY2F0aW9uL3ZuZC5leGFtcGxlLnJlZnZhbHNEr66trIJ4H2FwcGxpY2F0aW9uL3ZuZC5leGFtcGxlLnJlZnZhbHNErayrqg \ No newline at end of file +owB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGBowDZAjBEABEiMwFuRXhhbXBsZSBWZW5kb3ICbUV4YW1wbGUgTW9kZWwCwHQyMDMwLTEyLTAxVDE4OjMwOjAxWgMBAqMAgArAdDIwMzAtMTItMTNUMTg6MzA6MDJaC4KCeB9hcHBsaWNhdGlvbi92bmQuZXhhbXBsZS5yZWZ2YWxzRK-urayCeB9hcHBsaWNhdGlvbi92bmQuZXhhbXBsZS5yZWZ2YWxzRK2sq6o \ No newline at end of file diff --git a/coserv/testvectors/rv-class-simple-results-source-artifacts.cbor b/coserv/testvectors/rv-class-simple-results-source-artifacts.cbor index 8e29f1e9d3e0ceb42385bfd012dea9425eb709fb..fd01a9f9bc24771ae6f5e5a2326a8513196e7c20 100644 GIT binary patch delta 9 Qcmcc5_<(VO(L}>_02FBh^Z)<= delta 9 QcmaFBc%N~C(M03502FQm^#A|> diff --git a/coserv/testvectors/rv-class-simple-results-source-artifacts.diag b/coserv/testvectors/rv-class-simple-results-source-artifacts.diag index 09db6308..c3129480 100644 --- a/coserv/testvectors/rv-class-simple-results-source-artifacts.diag +++ b/coserv/testvectors/rv-class-simple-results-source-artifacts.diag @@ -3,13 +3,13 @@ / query / 1: { / artifact-type / 0: 2, / reference-values / / environment-selector / 1: { - / class / 0: [ + / class / 0: [ [ { / class-id / 0: 560(h'00112233'), / tagged-bytes / / vendor / 1: "Example Vendor", / model / 2: "Example Model" } - ] + ] ] }, / timestamp / 2: 0("2030-12-01T18:30:01Z"), / result-type / 3: 1 / source-artifacts / diff --git a/coserv/testvectors/rv-class-simple-results-source-artifacts.hex b/coserv/testvectors/rv-class-simple-results-source-artifacts.hex index aa403672..df3d8d80 100644 --- a/coserv/testvectors/rv-class-simple-results-source-artifacts.hex +++ b/coserv/testvectors/rv-class-simple-results-source-artifacts.hex @@ -1 +1 @@ -a30078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10081a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c02c074323033302d31322d30315431383a33303a30315a030102a300800ac074323033302d31322d31335431383a33303a30325a0b8282781f6170706c69636174696f6e2f766e642e6578616d706c652e72656676616c7344afaeadac82781f6170706c69636174696f6e2f766e642e6578616d706c652e72656676616c7344adacabaa +a30078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a1008181a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c02c074323033302d31322d30315431383a33303a30315a030102a300800ac074323033302d31322d31335431383a33303a30325a0b8282781f6170706c69636174696f6e2f766e642e6578616d706c652e72656676616c7344afaeadac82781f6170706c69636174696f6e2f766e642e6578616d706c652e72656676616c7344adacabaa diff --git a/coserv/testvectors/rv-class-simple-results.b64u b/coserv/testvectors/rv-class-simple-results.b64u index 2235c3a5..407fec27 100644 --- a/coserv/testvectors/rv-class-simple-results.b64u +++ b/coserv/testvectors/rv-class-simple-results.b64u @@ -1 +1 @@ -owB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGjANkCMEQAESIzAW5FeGFtcGxlIFZlbmRvcgJtRXhhbXBsZSBNb2RlbALAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwACogrAdDIwMzAtMTItMTNUMTg6MzA6MDJaAIGiAYHZAjBDq83vAoKhAKMA2QIwRAARIjMBbkV4YW1wbGUgVmVuZG9yAm1FeGFtcGxlIE1vZGVsgqEBogtrQ29tcG9uZW50IEECgoIBQaqCAkG7oQGiC2tDb21wb25lbnQgQgKCggFBzIICQd0 \ No newline at end of file +owB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGBowDZAjBEABEiMwFuRXhhbXBsZSBWZW5kb3ICbUV4YW1wbGUgTW9kZWwCwHQyMDMwLTEyLTAxVDE4OjMwOjAxWgMAAqIAgaIBgdkCMEOrze8CgqEAowDZAjBEABEiMwFuRXhhbXBsZSBWZW5kb3ICbUV4YW1wbGUgTW9kZWyCoQGiC2tDb21wb25lbnQgQQKCggFBqoICQbuhAaILa0NvbXBvbmVudCBCAoKCAUHMggJB3QrAdDIwMzAtMTItMTNUMTg6MzA6MDJa \ No newline at end of file diff --git a/coserv/testvectors/rv-class-simple-results.cbor b/coserv/testvectors/rv-class-simple-results.cbor index e4eb64545d8962db9582cd4ad08fae924868731a..b15fb6e86c57d52696f93dce46149d24bc5151a4 100644 GIT binary patch delta 36 rcmey(_=jw42%tQ4UKdSjYAAAtc(q;42+@x>f8$2 delta 39 vcmeyv_?vNp(ZqJmi80dRTn9>w42%tQ4UKdSjYAAAtc(q;42+^CmgfNg2Sp49 diff --git a/coserv/testvectors/rv-class-simple-results.diag b/coserv/testvectors/rv-class-simple-results.diag index 297e35b4..a5446b7a 100644 --- a/coserv/testvectors/rv-class-simple-results.diag +++ b/coserv/testvectors/rv-class-simple-results.diag @@ -3,19 +3,18 @@ / query / 1: { / artifact-type / 0: 2, / reference-values / / environment-selector / 1: { - / class / 0: [ + / class / 0: [ [ { / class-id / 0: 560(h'00112233'), / tagged-bytes / / vendor / 1: "Example Vendor", / model / 2: "Example Model" } - ] + ] ] }, / timestamp / 2: 0("2030-12-01T18:30:01Z"), / result-type / 3: 0 / collected-material / }, / results / 2: { - / expiry / 10: 0("2030-12-13T18:30:02Z"), / rvq / 0: [ { / authorities / 1: [ 560(h'abcdef') ], @@ -49,6 +48,7 @@ ] ] } - ] + ], + / expiry / 10: 0("2030-12-13T18:30:02Z") } } diff --git a/coserv/testvectors/rv-class-simple-results.hex b/coserv/testvectors/rv-class-simple-results.hex index 75d2e24f..4b5d82b3 100644 --- a/coserv/testvectors/rv-class-simple-results.hex +++ b/coserv/testvectors/rv-class-simple-results.hex @@ -1 +1 @@ -a30078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10081a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c02c074323033302d31322d30315431383a33303a30315a030002a20ac074323033302d31322d31335431383a33303a30325a0081a20181d9023043abcdef0282a100a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c82a101a20b6b436f6d706f6e656e7420410282820141aa820241bba101a20b6b436f6d706f6e656e7420420282820141cc820241dd +a30078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a1008181a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c02c074323033302d31322d30315431383a33303a30315a030002a20081a20181d9023043abcdef0282a100a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c82a101a20b6b436f6d706f6e656e7420410282820141aa820241bba101a20b6b436f6d706f6e656e7420420282820141cc820241dd0ac074323033302d31322d31335431383a33303a30325a diff --git a/coserv/testvectors/rv-class-simple.b64u b/coserv/testvectors/rv-class-simple.b64u index c561f23e..009dfb5b 100644 --- a/coserv/testvectors/rv-class-simple.b64u +++ b/coserv/testvectors/rv-class-simple.b64u @@ -1 +1 @@ -ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGjANkCMEQAESIzAW5FeGFtcGxlIFZlbmRvcgJtRXhhbXBsZSBNb2RlbALAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwE \ No newline at end of file +ogB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGBowDZAjBEABEiMwFuRXhhbXBsZSBWZW5kb3ICbUV4YW1wbGUgTW9kZWwCwHQyMDMwLTEyLTAxVDE4OjMwOjAxWgMB \ No newline at end of file diff --git a/coserv/testvectors/rv-class-simple.cbor b/coserv/testvectors/rv-class-simple.cbor index 2c8c7e40de06141b6f379ae09d1be631bf83a445..c076c62ea60604020d37674057babffc7fb00426 100644 GIT binary patch delta 7 OcmXRZonSQ4&=~*=;R3M$ delta 7 OcmXRdnP4>0*a-j&HSQ#5w85l+}GXelqfh!CE literal 0 HcmV?d00001 diff --git a/coserv/testvectors/rv-class-stateful.diag b/coserv/testvectors/rv-class-stateful.diag new file mode 100644 index 00000000..18a9d086 --- /dev/null +++ b/coserv/testvectors/rv-class-stateful.diag @@ -0,0 +1,29 @@ +{ + / profile / 0: "tag:example.com,2025:cc-platform#1.0.0", + / query / 1: { + / artifact-type / 0: 2, / reference-values / + / environment-selector / 1: { + / stateful-class / 0: [ + [ + / class / { + / class-id / 0: 560(h'00112233'), / tagged-bytes / + / vendor / 1: "Example Vendor", + / model / 2: "Example Model" + }, + / measurements / [ + / measurement-map / { + / mval / 1: { + / name / 11: "Component A", + / digests / 2: [ + [ 1, h'aa' ] + ] + } + } + ] + ] + ] + }, + / timestamp / 2: 0("2030-12-01T18:30:01Z"), + / result-type / 3: 1 / source-material / + } +} diff --git a/coserv/testvectors/rv-class-stateful.hex b/coserv/testvectors/rv-class-stateful.hex new file mode 100644 index 00000000..7b03c7d9 --- /dev/null +++ b/coserv/testvectors/rv-class-stateful.hex @@ -0,0 +1 @@ +a20078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a1008182a300d902304400112233016e4578616d706c652056656e646f72026d4578616d706c65204d6f64656c81a101a20b6b436f6d706f6e656e7420410281820141aa02c074323033302d31322d30315431383a33303a30315a0301 diff --git a/coserv/testvectors/rv-results.b64u b/coserv/testvectors/rv-results.b64u index 2187a3a0..9668041f 100644 --- a/coserv/testvectors/rv-results.b64u +++ b/coserv/testvectors/rv-results.b64u @@ -1 +1 @@ -owB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGhANkCMEWJmXhlVgLAdDIwMzAtMTItMDFUMTg6MzA6MDFaAwACogCBogGB2QIwQ6vN7wKCoQChANkCMEWJmXhlVoGiANglUDH7Wr8CPkmSqk6V-cFQO_oBogCiAGUxLjIuMwEZQAAB2QIpAgrAdDIwMzAtMTItMTNUMTg6MzA6MDJa \ No newline at end of file +owB4JnRhZzpleGFtcGxlLmNvbSwyMDI1OmNjLXBsYXRmb3JtIzEuMC4wAaQAAgGhAIGBoQDZAjBFiZl4ZVYCwHQyMDMwLTEyLTAxVDE4OjMwOjAxWgMAAqIAgaIBgdkCMEOrze8CgqEAoQDZAjBFiZl4ZVaBogDYJVAx-1q_Aj5JkqpOlfnBUDv6AaIAogBlMS4yLjMBGUAAAdkCKQIKwHQyMDMwLTEyLTEzVDE4OjMwOjAyWg \ No newline at end of file diff --git a/coserv/testvectors/rv-results.cbor b/coserv/testvectors/rv-results.cbor index 2360747958a82d00fd06e5b3adcda38dc140e81f..b1525c565df305354c875bfab7477446bc3242ae 100644 GIT binary patch delta 9 QcmdnOxRr5&(L}>$01=J@a{vGU delta 9 QcmdnWxP@_o(M01W01=Y|bN~PV diff --git a/coserv/testvectors/rv-results.diag b/coserv/testvectors/rv-results.diag index 3875fd2f..151b60ba 100644 --- a/coserv/testvectors/rv-results.diag +++ b/coserv/testvectors/rv-results.diag @@ -3,11 +3,11 @@ / query / 1: { 0: 2, 1: { - 0: [ + 0: [ [ { 0: 560(h'8999786556') } - ] + ] ] }, 2: 0("2030-12-01T18:30:01Z"), 3: 0 diff --git a/coserv/testvectors/rv-results.hex b/coserv/testvectors/rv-results.hex index 5f596eef..19165b57 100644 --- a/coserv/testvectors/rv-results.hex +++ b/coserv/testvectors/rv-results.hex @@ -1 +1 @@ -a30078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a10081a100d9023045899978655602c074323033302d31322d30315431383a33303a30315a030002a20081a20181d9023043abcdef0282a100a100d9023045899978655681a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a200a20065312e322e330119400001d90229020ac074323033302d31322d31335431383a33303a30325a +a30078267461673a6578616d706c652e636f6d2c323032353a63632d706c6174666f726d23312e302e3001a4000201a1008181a100d9023045899978655602c074323033302d31322d30315431383a33303a30315a030002a20081a20181d9023043abcdef0282a100a100d9023045899978655681a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a200a20065312e322e330119400001d90229020ac074323033302d31322d31335431383a33303a30325a diff --git a/go.mod b/go.mod index 65d859f0..fd104ba9 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,12 @@ toolchain go1.24.2 require ( fortio.org/safecast v1.0.0 - github.com/fxamacker/cbor/v2 v2.7.0 + github.com/fxamacker/cbor/v2 v2.8.0 github.com/google/uuid v1.3.0 github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/spf13/cast v1.4.1 github.com/stretchr/testify v1.10.0 + github.com/veraison/cmw v0.2.0 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff github.com/veraison/go-cose v1.2.1 github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca @@ -28,7 +29,6 @@ require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/segmentio/asm v1.2.0 // indirect - github.com/veraison/cmw v0.2.0 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/sys v0.28.0 // indirect diff --git a/go.sum b/go.sum index 4f3e07fa..3a057c93 100644 --- a/go.sum +++ b/go.sum @@ -7,10 +7,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= -github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= +github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= From 6aa97e4f4d27a9ee58923249b8d84e483e131bb0 Mon Sep 17 00:00:00 2001 From: Yogesh Deshpande Date: Fri, 11 Jul 2025 18:41:04 +0100 Subject: [PATCH 110/110] feat(coev): implement minimalistic concise evidence library (#205) * feat(coev): implement minimalistic concise evidence library Fixes #185 Signed-off-by: Yogesh Deshpande * Incorporate Sergei's review comments Signed-off-by: Yogesh Deshpande * Addressing partial comments Signed-off-by: Yogesh Deshpande * Correction to Profile Signed-off-by: Yogesh Deshpande * Some more comments addressed Signed-off-by: Yogesh Deshpande * Further comments Signed-off-by: Yogesh Deshpande * Incorporate remainder of Thomas Comments Signed-off-by: Yogesh Deshpande --------- Signed-off-by: Yogesh Deshpande --- README.md | 2 + coev/cbor.go | 62 ++++ coev/concise_evidence.go | 200 +++++++++++++ coev/concise_evidence_test.go | 109 +++++++ coev/coswid_evidence.go | 30 ++ coev/coswidtriple.go | 66 +++++ coev/coswidtriple_test.go | 58 ++++ coev/ev_triples.go | 181 ++++++++++++ coev/evidence_id.go | 202 +++++++++++++ coev/evidence_id_test.go | 63 ++++ coev/example_test.go | 432 ++++++++++++++++++++++++++++ coev/extensions.go | 17 ++ coev/test_vars.go | 22 ++ coev/testcases/ce-coswid.cbor | Bin 0 -> 194 bytes coev/testcases/ce-evidence.cbor | Bin 0 -> 268 bytes coev/testcases/ce-identity.cbor | Bin 0 -> 1002 bytes coev/testcases/regen-from-src.sh | 21 ++ coev/testcases/src/ce-coswid.diag | 39 +++ coev/testcases/src/ce-evidence.diag | 23 ++ coev/testcases/src/ce-identity.diag | 27 ++ 20 files changed, 1554 insertions(+) create mode 100644 coev/cbor.go create mode 100644 coev/concise_evidence.go create mode 100644 coev/concise_evidence_test.go create mode 100644 coev/coswid_evidence.go create mode 100644 coev/coswidtriple.go create mode 100644 coev/coswidtriple_test.go create mode 100644 coev/ev_triples.go create mode 100644 coev/evidence_id.go create mode 100644 coev/evidence_id_test.go create mode 100644 coev/example_test.go create mode 100644 coev/extensions.go create mode 100644 coev/test_vars.go create mode 100644 coev/testcases/ce-coswid.cbor create mode 100644 coev/testcases/ce-evidence.cbor create mode 100644 coev/testcases/ce-identity.cbor create mode 100755 coev/testcases/regen-from-src.sh create mode 100644 coev/testcases/src/ce-coswid.diag create mode 100644 coev/testcases/src/ce-evidence.diag create mode 100644 coev/testcases/src/ce-identity.diag diff --git a/README.md b/README.md index 1ec0f167..d8cd53fe 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ [![linters](https://github.com/veraison/corim/actions/workflows/linters.yml/badge.svg)](https://github.com/veraison/corim/actions/workflows/linters.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/veraison/corim.svg)](https://pkg.go.dev/github.com/veraison/corim) + The [`corim/corim`](corim) and [`corim/comid`](comid) packages provide a golang API for low-level manipulation of [Concise Reference Integrity Manifest (CoRIM)](https://datatracker.ietf.org/doc/draft-ietf-rats-corim/) and Concise Module Identifier (CoMID) tags respectively. +The [`corim/coev`](coev) package provides a minimal golang implementation of TCG Concise Evidence CDDL as documented [here](https://github.com/TrustedComputingGroup/dice-coev/blob/main/concise-evidence.cddl) The [`corim/coserv`](coserv) package provides a golang API for working with [Concise Selector for Endorsements and Reference Values](https://datatracker.ietf.org/doc/draft-howard-rats-coserv). > [!NOTE] diff --git a/coev/cbor.go b/coev/cbor.go new file mode 100644 index 00000000..64b07a21 --- /dev/null +++ b/coev/cbor.go @@ -0,0 +1,62 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "reflect" + + cbor "github.com/fxamacker/cbor/v2" + "github.com/veraison/corim/comid" +) + +var ( + em, emError = initCBOREncMode() + dm, dmError = initCBORDecMode() + ConciseEvidenceTag = []byte{0xd9, 0x02, 0x3B} + coevTagMap = map[uint64]interface{}{ + 37: comid.TaggedUUID{}, + } +) + +func coevTags() cbor.TagSet { + opts := cbor.TagOptions{ + EncTag: cbor.EncTagRequired, + DecTag: cbor.DecTagRequired, + } + + tags := cbor.NewTagSet() + + for tag, typ := range coevTagMap { + if err := tags.Add(opts, reflect.TypeOf(typ), tag); err != nil { + panic(err) + } + } + + return tags +} + +func initCBOREncMode() (en cbor.EncMode, err error) { + encOpt := cbor.EncOptions{ + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + TimeTag: cbor.EncTagRequired, + } + return encOpt.EncModeWithTags(coevTags()) +} + +func initCBORDecMode() (dm cbor.DecMode, err error) { + decOpt := cbor.DecOptions{ + IndefLength: cbor.IndefLengthForbidden, + } + return decOpt.DecModeWithTags(coevTags()) +} + +func init() { + if emError != nil { + panic(emError) + } + if dmError != nil { + panic(dmError) + } +} diff --git a/coev/concise_evidence.go b/coev/concise_evidence.go new file mode 100644 index 00000000..8f2d10c6 --- /dev/null +++ b/coev/concise_evidence.go @@ -0,0 +1,200 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "bytes" + "errors" + "fmt" + + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" +) + +type ConciseEvidence struct { + EvTriples EvTriples `cbor:"0,keyasint" json:"ev-triples"` + EvidenceID *EvidenceID `cbor:"1,keyasint,omitempty" json:"evidence-id,omitempty"` + Profile *eat.Profile `cbor:"2,keyasint,omitempty" json:"profile,omitempty"` + Extensions +} + +// NewConciseEvidence instantiates an empty ConciseEvidence +func NewConciseEvidence() *ConciseEvidence { + return &ConciseEvidence{} +} + +// AddTriples adds Evidence Triples to Concise Evidence +func (o *ConciseEvidence) AddTriples(evTriples *EvTriples) error { + if o != nil { + if evTriples == nil { + return errors.New("no evidence triples") + } + + if err := evTriples.Valid(); err != nil { + return fmt.Errorf("invalid evidence triples: %w", err) + } + o.EvTriples = *evTriples + } + return nil +} + +// AddEvidenceID adds EvidenceID to ConciseEvidence +func (o *ConciseEvidence) AddEvidenceID(evidenceID *EvidenceID) error { + if o != nil { + if evidenceID == nil { + return errors.New("no evidence id supplied") + } + if err := evidenceID.Valid(); err != nil { + return fmt.Errorf("invalid EvidenceID: %w", err) + } + o.EvidenceID = evidenceID + } + return nil +} + +// AddProfile adds a chosen profile to ConciseEvidence +func (o *ConciseEvidence) AddProfile(urlOrOID string) error { + p, err := eat.NewProfile(urlOrOID) + if err != nil { + return err + } + o.Profile = p + return nil +} + +// nolint:gocritic +func (o ConciseEvidence) Valid() error { + if err := o.EvTriples.Valid(); err != nil { + return fmt.Errorf("invalid EvTriples: %w", err) + } + if o.EvidenceID != nil { + if err := o.EvidenceID.Valid(); err != nil { + return fmt.Errorf("invalid EvidenceID: %w", err) + } + } + + return nil +} + +// RegisterExtensions registers a struct as a collections of extensions +func (o *ConciseEvidence) RegisterExtensions(exts extensions.Map) error { + evTriplesMap := extensions.NewMap() + for p, v := range exts { + switch p { + case ExtConciseEvidence: + o.Register(v) + default: + evTriplesMap.Add(p, v) + } + } + return o.EvTriples.RegisterExtensions(evTriplesMap) +} + +// GetExtensions returns previously registered extension +func (o *ConciseEvidence) GetExtensions() extensions.IMapValue { + return o.IMapValue +} + +// ToCBOR serializes the target ConciseEvidence to CBOR +// nolint:gocritic +func (o ConciseEvidence) ToCBOR() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + return encoding.SerializeStructToCBOR(em, &o) +} + +// FromCBOR deserializes a CBOR-encoded ConciseEvidence into the target ConciseEvidence +func (o *ConciseEvidence) FromCBOR(data []byte) error { + if err := encoding.PopulateStructFromCBOR(dm, data, o); err != nil { + return err + } + return o.Valid() +} + +// ToJSON serializes the target ConciseEvidence to JSON +// nolint:gocritic +func (o ConciseEvidence) ToJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + return encoding.SerializeStructToJSON(&o) +} + +// FromJSON deserializes a JSON-encoded ConciseEvidence into the target ConciseEvidence +func (o *ConciseEvidence) FromJSON(data []byte) error { + if err := encoding.PopulateStructFromJSON(data, o); err != nil { + return err + } + return o.Valid() +} + +type TaggedConciseEvidence ConciseEvidence + +// NewTagged Concise Evidence creates a Tagged Concise Evidence from a supplied Concise Evidence +func NewTaggedConciseEvidence(ev *ConciseEvidence) (*TaggedConciseEvidence, error) { + var tce TaggedConciseEvidence + if ev == nil { + return nil, errors.New("non existent concise evidence") + } + if err := ev.Valid(); err != nil { + return nil, fmt.Errorf("concise Evidence is not valid: %w", err) + } + tce = TaggedConciseEvidence(*ev) + return &tce, nil +} + +// Valid checks the validity of TaggedConciseEvidence +// nolint:gocritic +func (o TaggedConciseEvidence) Valid() error { + c := ConciseEvidence(o) + return c.Valid() +} + +// ToCBOR serializes the target TaggedConciseEvidence to CBOR +// nolint:gocritic +func (o TaggedConciseEvidence) ToCBOR() ([]byte, error) { + ce := ConciseEvidence(o) + if err := ce.Valid(); err != nil { + return nil, err + } + + data, err := ce.ToCBOR() + if err != nil { + return nil, fmt.Errorf("unable to serialize the data: %w", err) + } + return append(ConciseEvidenceTag, data...), nil +} + +// FromCBOR deserializes a CBOR-encoded date into the TaggedConciseEvidence +func (o *TaggedConciseEvidence) FromCBOR(data []byte) error { + if len(data) < 3 { + return errors.New("input CBOR data too short") + } + if !bytes.Equal(data[:3], ConciseEvidenceTag) { + return errors.New("did not see concise evidence tag") + } + if err := encoding.PopulateStructFromCBOR(dm, data[3:], o); err != nil { + return err + } + return o.Valid() + +} + +// FromJSON deserializes a JSON-encoded TaggedConciseEvidence into the target TaggedConciseEvidence +func (o *TaggedConciseEvidence) FromJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// ToJSON serializes the target TaggedConciseEvidence to JSON +// nolint:gocritic +func (o TaggedConciseEvidence) ToJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + return encoding.SerializeStructToJSON(&o) +} diff --git a/coev/concise_evidence_test.go b/coev/concise_evidence_test.go new file mode 100644 index 00000000..15b2f38e --- /dev/null +++ b/coev/concise_evidence_test.go @@ -0,0 +1,109 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/extensions" +) + +func TestConciseEvidence_NewConciseEvidence(t *testing.T) { + coev := NewConciseEvidence() + require.NotNil(t, coev) +} + +func TestConciseEvidence_AddTriples_NOK(t *testing.T) { + expectedErr := "no evidence triples" + var ev *EvTriples + coev := &ConciseEvidence{} + err := coev.AddTriples(ev) + assert.EqualError(t, err, expectedErr) + expectedErr = "invalid evidence triples: no Triples set inside EvTriples" + ev = &EvTriples{} + err = coev.AddTriples(ev) + assert.EqualError(t, err, expectedErr) +} + +func TestConciseEvidence_AddEvidenceID(t *testing.T) { + coev := &ConciseEvidence{} + ev := MustNewUUIDEvidenceID(TestUUID) + require.NotNil(t, ev) + err := coev.AddEvidenceID(ev) + require.NoError(t, err) +} + +func TestConciseEvidence_AddEvidenceID_NOK(t *testing.T) { + coev := &ConciseEvidence{} + expectedErr := "invalid EvidenceID: no EvidenceID" + var e EvidenceID + err := coev.AddEvidenceID(&e) + assert.EqualError(t, err, expectedErr) +} + +func TestConciseEvidence_AddProfile(t *testing.T) { + coev := &ConciseEvidence{} + err := coev.AddProfile(TestProfile) + require.NoError(t, err) + +} + +func TestConciseEvidence_AddProfile_NOK(t *testing.T) { + coev := &ConciseEvidence{} + expectedErr := "profile string must be an absolute URL or an ASN.1 OID: no valid OID" + var p string + err := coev.AddProfile(p) + assert.EqualError(t, err, expectedErr) + expectedErr = `profile string must be an absolute URL or an ASN.1 OID: failed to extract OID from string: strconv.Atoi: parsing "not": invalid syntax` + p = "not" + err = coev.AddProfile(p) + assert.EqualError(t, err, expectedErr) +} + +func TestConciseEvidence_Valid_NOK(t *testing.T) { + expectedErr := "invalid EvTriples: no Triples set inside EvTriples" + coev := &ConciseEvidence{} + err := coev.Valid() + assert.EqualError(t, err, expectedErr) +} + +func Test_ConciseEvidence_Extensions(t *testing.T) { + c := NewConciseEvidence() + assert.Nil(t, c.GetExtensions()) + assert.Equal(t, "", c.MustGetString("myparam")) + + err := c.Set("myparam", "test-param") + assert.EqualError(t, err, "extension not found: myparam") + + type CoEvExt struct { + MyParam string `cbor:"-1,keyasint" json:"myparam"` + } + + extMap := extensions.NewMap(). + Add(ExtConciseEvidence, &CoEvExt{}). + Add(ExtEvTriples, &struct{}{}). + Add(ExtEvidenceTriples, &struct{}{}). + Add(ExtEvidenceTriplesFlags, &struct{}{}) + + err = c.RegisterExtensions(extMap) + require.NoError(t, err) + + err = c.Set("myparam", "test-param") + assert.NoError(t, err) + assert.Equal(t, "test-param", c.MustGetString("-1")) +} + +func Test_ConciseEvidence_RegisterExtensions_NOK(t *testing.T) { + expectedErr := `unexpected extension point: "myPoint"` + c := NewConciseEvidence() + type CoEvExt struct { + MyParam string `cbor:"-1,keyasint" json:"myparam"` + } + extMap := extensions.NewMap(). + Add("myPoint", &CoEvExt{}) + err := c.RegisterExtensions(extMap) + assert.EqualError(t, err, expectedErr) +} diff --git a/coev/coswid_evidence.go b/coev/coswid_evidence.go new file mode 100644 index 00000000..323f4618 --- /dev/null +++ b/coev/coswid_evidence.go @@ -0,0 +1,30 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "github.com/veraison/corim/comid" + "github.com/veraison/swid" +) + +// CoSWIDEvidenceMap is the Map to carry CoSWID Evidence +type CoSWIDEvidenceMap struct { + TagID *swid.TagID `cbor:"0,keyasint,omitempty" json:"tagId,omitempty"` + Evidence swid.Evidence `cbor:"1,keyasint,omitempty" json:"evidence,omitempty"` + AuthorizedBy *comid.CryptoKey `cbor:"2,keyasint,omitempty" json:"authorized-by,omitempty"` +} + +type CoSWIDEvidence []CoSWIDEvidenceMap + +func NewCoSWIDEvidence() *CoSWIDEvidence { + return &CoSWIDEvidence{} +} + +func (o *CoSWIDEvidence) AddCoSWIDEvidenceMap(e *CoSWIDEvidenceMap) *CoSWIDEvidence { + if o == nil { + o = NewCoSWIDEvidence() + } + *o = append(*o, *e) + return o +} diff --git a/coev/coswidtriple.go b/coev/coswidtriple.go new file mode 100644 index 00000000..0b0f260c --- /dev/null +++ b/coev/coswidtriple.go @@ -0,0 +1,66 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "errors" + "fmt" + + "github.com/veraison/corim/comid" +) + +// CoSWIDTriple stores a CoSWID Evidence +// pertaining to an Environment +type CoSWIDTriple struct { + _ struct{} `cbor:",toarray"` + Environment comid.Environment `json:"environment"` + Evidence CoSWIDEvidence `json:"coswid-evidence"` +} + +func NewCoSWIDTriple() *CoSWIDTriple { + return &CoSWIDTriple{} +} + +func (o *CoSWIDTriple) AddEnvironment(e *comid.Environment) error { + if e == nil { + return errors.New("no environment to add") + } + if err := e.Valid(); err != nil { + return fmt.Errorf("environment is not valid: %w", err) + } + + o.Environment = *e + return nil +} + +func (o *CoSWIDTriple) AddEvidence(e *CoSWIDEvidenceMap) error { + + if e == nil { + return errors.New("no evidence map to add") + } + + if len(o.Evidence) == 0 { + o.Evidence = *NewCoSWIDEvidence() + } + + o.Evidence = append(o.Evidence, *e) + return nil +} + +func (o CoSWIDTriple) Valid() error { + if err := o.Environment.Valid(); err != nil { + return fmt.Errorf("environment validation failed: %w", err) + } + + if len(o.Evidence) == 0 { + return errors.New("no evidence entry in the CoSWIDTriple") + } + return nil +} + +type CoSWIDTriples []CoSWIDTriple + +func NewCoSWIDTriples() *CoSWIDTriples { + return &CoSWIDTriples{} +} diff --git a/coev/coswidtriple_test.go b/coev/coswidtriple_test.go new file mode 100644 index 00000000..8b292eb3 --- /dev/null +++ b/coev/coswidtriple_test.go @@ -0,0 +1,58 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" + "github.com/veraison/swid" +) + +func TestCoSWIDTriple_NewCoSWIDTriple(t *testing.T) { + s := NewCoSWIDTriple() + require.NotNil(t, s) +} + +func TestCoSWIDTriple_AddEnvironment(t *testing.T) { + s := &CoSWIDTriple{} + tv := &comid.Environment{ + Class: comid.NewClassUUID(TestUUID), + } + + err := s.AddEnvironment(tv) + require.Nil(t, err) +} + +func TestCoSWIDTriple_AddEnvironment_NOK(t *testing.T) { + expectedErr := "no environment to add" + s := &CoSWIDTriple{} + var tv *comid.Environment + err := s.AddEnvironment(tv) + assert.EqualError(t, err, expectedErr) + expectedErr = "environment is not valid: environment must not be empty" + tv = &comid.Environment{} + err = s.AddEnvironment(tv) + assert.EqualError(t, err, expectedErr) +} + +func TestCoSWIDTriple_AddEvidence(t *testing.T) { + s := &CoSWIDTriple{} + tv := &CoSWIDEvidenceMap{ + TagID: swid.NewTagID(TestTag), + Evidence: swid.Evidence{Date: TestDate, DeviceID: TestDeviceID}, + } + err := s.AddEvidence(tv) + require.Nil(t, err) +} + +func TestCoSWIDTriple_AddEvidence_NOK(t *testing.T) { + expectedErr := "no evidence map to add" + s := &CoSWIDTriple{} + var tv *CoSWIDEvidenceMap + err := s.AddEvidence(tv) + assert.EqualError(t, err, expectedErr) +} diff --git a/coev/ev_triples.go b/coev/ev_triples.go new file mode 100644 index 00000000..73ee3bd4 --- /dev/null +++ b/coev/ev_triples.go @@ -0,0 +1,181 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "errors" + "fmt" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" +) + +type EvTriples struct { + EvidenceTriples *comid.ValueTriples `cbor:"0,keyasint,omitempty" json:"evidence-triples,omitempty"` + IdentityTriples *comid.KeyTriples `cbor:"1,keyasint,omitempty" json:"identity-triples,omitempty"` + CoSWIDTriples *CoSWIDTriples `cbor:"4,keyasint,omitempty" json:"coswid-triples,omitempty"` + AttestKeysTriples *comid.KeyTriples `cbor:"5,keyasint,omitempty" json:"attestkey-triples,omitempty"` + Extensions +} + +func NewEvTriples() *EvTriples { + return &EvTriples{} +} + +func (o EvTriples) Valid() error { + // Check if triples are set ? + if o.EvidenceTriples == nil && + o.IdentityTriples == nil && + o.CoSWIDTriples == nil && + o.AttestKeysTriples == nil { + return errors.New("no Triples set inside EvTriples") + } + + if o.EvidenceTriples != nil { + if err := o.EvidenceTriples.Valid(); err != nil { + return fmt.Errorf("invalid EvidenceTriples: %w", err) + } + } + + if o.IdentityTriples != nil { + for i, identity := range *o.IdentityTriples { + if err := identity.Valid(); err != nil { + return fmt.Errorf("invalid IdentityTriple at index: %d, %w", i, err) + } + } + } + + if o.CoSWIDTriples != nil { + for i, swid := range *o.CoSWIDTriples { + if err := swid.Valid(); err != nil { + return fmt.Errorf("invalid CoSWIDTriple at index: %d, %w", i, err) + } + } + } + + if o.AttestKeysTriples != nil { + for i, key := range *o.AttestKeysTriples { + if err := key.Valid(); err != nil { + return fmt.Errorf("invalid AttestKeysTriple at index: %d, %w", i, err) + } + } + } + + return nil +} + +func (o *EvTriples) AddEvidenceTriple(val *comid.ValueTriple) *EvTriples { + if o != nil { + if o.EvidenceTriples == nil { + o.EvidenceTriples = comid.NewValueTriples() + } + o.EvidenceTriples.Add(val) + } + + return o +} + +func (o *EvTriples) AddCoSWIDTriple(val *CoSWIDTriple) *EvTriples { + if o != nil { + if o.CoSWIDTriples == nil { + o.CoSWIDTriples = NewCoSWIDTriples() + } + *o.CoSWIDTriples = append(*o.CoSWIDTriples, *val) + } + return o +} + +func (o *EvTriples) AddIdentityTriple(val *comid.KeyTriple) *EvTriples { + if o != nil { + if o.IdentityTriples == nil { + o.IdentityTriples = comid.NewKeyTriples() + } + *o.IdentityTriples = append(*o.IdentityTriples, *val) + } + + return o +} + +func (o *EvTriples) AddAttestKeyTriple(val *comid.KeyTriple) *EvTriples { + if o != nil { + if o.AttestKeysTriples == nil { + o.AttestKeysTriples = comid.NewKeyTriples() + } + *o.AttestKeysTriples = append(*o.AttestKeysTriples, *val) + } + + return o +} + +func (o *EvTriples) RegisterExtensions(exts extensions.Map) error { + EvidenceTriplesExts := extensions.NewMap() + for p, v := range exts { + switch p { + case ExtEvTriples: + o.Register(v) + case ExtEvidenceTriples: + if o.EvidenceTriples == nil { + o.EvidenceTriples = comid.NewValueTriples() + } + EvidenceTriplesExts[comid.ExtMval] = v + case ExtEvidenceTriplesFlags: + if o.EvidenceTriples == nil { + o.EvidenceTriples = comid.NewValueTriples() + } + EvidenceTriplesExts[comid.ExtFlags] = v + default: + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + } + if len(EvidenceTriplesExts) != 0 { + return o.EvidenceTriples.RegisterExtensions(EvidenceTriplesExts) + } + return nil +} + +// GetExtensions returns previously registered extension +func (o *EvTriples) GetExtensions() extensions.IMapValue { + return o.IMapValue +} + +// UnmarshalCBOR deserializes from CBOR +func (o *EvTriples) UnmarshalCBOR(data []byte) error { + return encoding.PopulateStructFromCBOR(dm, data, o) +} + +// MarshalCBOR serializes to CBOR +func (o EvTriples) MarshalCBOR() ([]byte, error) { + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.EvidenceTriples != nil && o.EvidenceTriples.IsEmpty() { + o.EvidenceTriples = nil + } + // as of now there are no further extensions in EvTriples + return encoding.SerializeStructToCBOR(em, o) +} + +// UnmarshalJSON deserializes from JSON +func (o *EvTriples) UnmarshalJSON(data []byte) error { + return encoding.PopulateStructFromJSON(data, o) +} + +// MarshalJSON serializes to JSON +func (o EvTriples) MarshalJSON() ([]byte, error) { + // If extensions have been registered, the collection will exist, but + // might be empty. If that is the case, set it to nil to avoid + // marshaling an empty list (and let the marshaller omit the claim + // instead). Note that since the receiver was passed by value, we do not + // need to worry about saving the field's value before setting it to + // nil. + if o.EvidenceTriples != nil && o.EvidenceTriples.IsEmpty() { + o.EvidenceTriples = nil + } + + return encoding.SerializeStructToJSON(o) +} diff --git a/coev/evidence_id.go b/coev/evidence_id.go new file mode 100644 index 00000000..cd3f5e37 --- /dev/null +++ b/coev/evidence_id.go @@ -0,0 +1,202 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/google/uuid" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" +) + +// EvidenceID stores evidence identities. The currently supported format is UUID. +type EvidenceID struct { + Value IEvidenceValue +} + +// NewEvidenceID creates a new evidence with the value of the specified type +// populated using the provided value. +func NewEvidenceID(val any, typ string) (*EvidenceID, error) { + factory, ok := evidenceIDValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unknown EvidenceID type: %s", typ) + } + + return factory(val) +} + +// Valid checks for the validity of given EvidenceID +func (o EvidenceID) Valid() error { + if o.String() == "" { + return errors.New("no EvidenceID") + } + _, err := o.GetUUID() + if err != nil { + return fmt.Errorf("unable to fetch valid UUID: %w", err) + } + return nil +} + +// String returns a printable string of the EvidenceID value. UUIDs use the +// canonical 8-4-4-4-12 format. +func (o EvidenceID) String() string { + if o.Value == nil { + return "" + } + + return o.Value.String() +} + +// Type returns a string naming the type of the underlying EvidenceID value. +func (o EvidenceID) Type() string { + return o.Value.Type() +} + +// Bytes returns a []byte containing the bytes of the underlying EvidenceID +// value. +func (o EvidenceID) Bytes() []byte { + return o.Value.Bytes() +} + +// MarshalCBOR serializes the target EvidenceID to CBOR +func (o EvidenceID) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.Value) +} + +func (o *EvidenceID) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.Value) +} + +// UnmarshalJSON deserializes the supplied JSON object into the target EvidenceID +// The evidence object must have the following shape: +// +// { +// "type": "", +// "value": +// } +// +// where must be one of the known IEvidenceValue implementation +// type names (available in the base implementation: "uuid"), and +// is the JSON encoding of the evidence value. The exact +// encoding is dependent. For the base implmentation types it is +// +// uuid: standard UUID string representation, e.g. "550e8400-e29b-41d4-a716-446655440000" + +//nolint:dupl +func (o *EvidenceID) UnmarshalJSON(data []byte) error { + var tnv encoding.TypeAndValue + + if err := json.Unmarshal(data, &tnv); err != nil { + return fmt.Errorf("EvidenceID decoding failure: %w", err) + } + + decoded, err := NewEvidenceID(nil, tnv.Type) + if err != nil { + return err + } + + if err := json.Unmarshal(tnv.Value, &decoded.Value); err != nil { + return fmt.Errorf( + "cannot unmarshal EvidenceID: %w", + err, + ) + } + + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value + + return nil +} + +// MarshalJSON serializes the EvidenceID into a JSON object. +func (o EvidenceID) MarshalJSON() ([]byte, error) { + valueBytes, err := json.Marshal(o.Value) + if err != nil { + return nil, err + } + + value := encoding.TypeAndValue{ + Type: o.Value.Type(), + Value: valueBytes, + } + + return json.Marshal(value) +} + +// SetUUID sets the identity of the target Evidence to the supplied UUID +func (o *EvidenceID) SetUUID(val uuid.UUID) *EvidenceID { + if o != nil { + o.Value = comid.TaggedUUID(val) + } + return o +} + +// GetUUID returns a UUID, i.e. 128 bit (16 byte) Universal Unique IDentifier as defined +// in RFC4122, from the target Evidence +func (o EvidenceID) GetUUID() (comid.UUID, error) { + switch t := o.Value.(type) { + case *comid.TaggedUUID: + return comid.UUID(*t), nil + case comid.TaggedUUID: + return comid.UUID(t), nil + default: + return comid.UUID{}, fmt.Errorf("evidence-id type is: %T", t) + } +} + +// IEvidenceValue is the interface implemented by all EvidenceID value +// implementations. +type IEvidenceValue interface { + extensions.ITypeChoiceValue + + Bytes() []byte +} + +// NewUUIDEvidenceID instantiates a new EvidenceID from the +// supplied val, which is a UUID in a string format or a byte slice +func NewUUIDEvidenceID(val any) (*EvidenceID, error) { + if val == nil { + return &EvidenceID{&comid.TaggedUUID{}}, nil + } + + ret, err := comid.NewTaggedUUID(val) + if err != nil { + return nil, err + } + + return &EvidenceID{ret}, nil +} + +// MustNewUUIDEvidenceID is like NewUUIDEvidenceID except it does not return an +// error, assuming that the provided value is valid. It panics if that isn't +// the case. +func MustNewUUIDEvidenceID(val any) *EvidenceID { + ret, err := NewUUIDEvidenceID(val) + if err != nil { + panic(err) + } + + return ret +} + +// IEvidenceValueFactory defines the signature for the factory functions that may be +// registered using RegisterEvidenceType to provide a new implementation of the +// corresponding type choice. The factory function should create a new *EvidenceID +// with the underlying value created based on the provided input. The range of +// valid inputs is up to the specific type choice implementation, however it +// _must_ accept nil as one of the inputs, and return the Zero value for +// implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IEvidenceFactory func(any) (*EvidenceID, error) + +var evidenceIDValueRegister = map[string]IEvidenceFactory{ + comid.UUIDType: NewUUIDEvidenceID, +} diff --git a/coev/evidence_id_test.go b/coev/evidence_id_test.go new file mode 100644 index 00000000..873a61f9 --- /dev/null +++ b/coev/evidence_id_test.go @@ -0,0 +1,63 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEvidenceID_NewEvidenceID(t *testing.T) { + ev, err := NewEvidenceID(TestUUIDString, "uuid") + require.NoError(t, err) + require.NotNil(t, ev) +} + +func TestEvidenceID_SetUUID_OK(t *testing.T) { + ev := &EvidenceID{} + testUUID, err := uuid.Parse(TestUUIDString) + require.NoError(t, err) + i := ev.SetUUID(testUUID) + require.NotNil(t, i) +} + +func TestEvidenceID_GetUUID_OK(t *testing.T) { + ev := MustNewUUIDEvidenceID(TestUUID) + require.NotNil(t, ev) + u, err := ev.GetUUID() + assert.Nil(t, err) + assert.Equal(t, u, TestUUID) +} + +func TestEvidence_GetUUID_NOK(t *testing.T) { + ev := &EvidenceID{} + expectedErr := "evidence-id type is: " + _, err := ev.GetUUID() + assert.EqualError(t, err, expectedErr) +} + +func TestEvidenceID_UnmarshalJSON_OK(t *testing.T) { + for _, tv := range []struct { + Name string + Input string + }{ + { + Name: "valid input test 1", + Input: `{ "type": "uuid", "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" }`, + }, + { + Name: "valid input test 2", + Input: `{ "type": "uuid", "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfb"}`, + }, + } { + t.Run(tv.Name, func(t *testing.T) { + var actual EvidenceID + err := actual.UnmarshalJSON([]byte(tv.Input)) + require.NoError(t, err) + }) + } +} diff --git a/coev/example_test.go b/coev/example_test.go new file mode 100644 index 00000000..e00d717a --- /dev/null +++ b/coev/example_test.go @@ -0,0 +1,432 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + _ "embed" + "fmt" + "log" + "testing" + + "github.com/veraison/corim/comid" + "github.com/veraison/swid" +) + +func Example_encode_EvidenceTriples() { + coev := NewConciseEvidence() + err := coev.AddTriples(NewEvTriples().AddEvidenceTriple( + &comid.ValueTriple{ + Environment: comid.Environment{ + Class: comid.NewClassOID(comid.TestOID). + SetVendor("ACME Ltd."). + SetModel("RoadRunner"). + SetLayer(0). + SetIndex(1), + }, + Measurements: *comid.NewMeasurements(). + Add( + comid.MustNewUUIDMeasurement(TestUUID). + SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). + SetSVN(2). + AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + SetFlagsTrue(comid.FlagIsDebug). + SetFlagsFalse(comid.FlagIsSecure). + SetSerialNumber("C02X70VHJHD5"). + SetUEID(comid.TestUEID). + SetUUID(TestUUID). + SetMACaddr(comid.MACaddr(comid.TestMACaddr)). + SetIPaddr(comid.TestIPaddr), + ), + }, + ), + ) + if err != nil { + log.Fatalf("could not get new concise evidence: %v", err) + } + err = coev.AddEvidenceID(MustNewUUIDEvidenceID(TestUUID)) + if err != nil { + log.Fatalf("could not add EvidenceID: %v", err) + } + err = coev.AddProfile(TestProfile) + if err != nil { + log.Fatalf("could not add profile: %v", err) + } + cbor, err := coev.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } + + json, err := coev.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } + + // Output: + // a300a1008182a100a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040181a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa01d8255031fb5abf023e4992aa4e95f9c1503bfa026f68747470733a2f2f6162632e636f6d + // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}]},"evidence-id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"profile":"https://abc.com"} + +} + +func Example_encode_IdentityTriples() { + coev := NewConciseEvidence() + err := coev.AddTriples(NewEvTriples().AddIdentityTriple( + &comid.KeyTriple{ + Environment: comid.Environment{ + Class: comid.NewClassUUID(TestUUID). + SetVendor("ACME Inc"). + SetModel("ACME RoadRunner"). + SetLayer(1), + }, + VerifKeys: *comid.NewCryptoKeys(). + Add(comid.MustNewPKIXBase64Key(comid.TestECPubKey)). + Add(comid.MustNewPKIXBase64Cert(comid.TestCert)). + Add(comid.MustNewPKIXBase64CertPath(comid.TestCertPath)), + }, + ), + ) + + if err != nil { + log.Fatalf("could not get new concise evidence: %v", err) + } + cbor, err := coev.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } + + json, err := coev.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } + + // Output: + // a100a1018182a100a400d8255031fb5abf023e4992aa4e95f9c1503bfa016841434d4520496e63026f41434d4520526f616452756e6e6572030183d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2dd9022b7902c82d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949423454434341596567417749424167495547687241394d337951494671636b413276366e5165776d4633304977436759494b6f5a497a6a3045417749770a5254454c4d416b474131554542684d4351565578457a415242674e564241674d436c4e766257557455335268644755784954416642674e5642416f4d47456c750a64475679626d5630494664705a47647064484d6755485235494578305a444167467730794d7a41354d4451784d5441784e446861474138794d4455784d4445780a4f5445784d4445304f466f775254454c4d416b474131554542684d4351565578457a415242674e564241674d436c4e76625755745533526864475578495441660a42674e5642416f4d47456c7564475679626d5630494664705a47647064484d6755485235494578305a44425a4d424d4742797147534d343941674547434371470a534d3439417745484130494142467451623668667636387641566d75325244464e63574742784550415a53302b4442527a6d6a7669416b37534c367848684b610a2f37483077442b7568396f516833572b527846797255537148656b433344697a774e576a557a42524d423047413155644467515742425157704e5062366557440a534d2f2b6a7770627a6f4f33694867344c54416642674e5648534d454744415767425157704e506236655744534d2f2b6a7770627a6f4f33694867344c5441500a42674e5648524d4241663845425441444151482f4d416f4743437147534d343942414d43413067414d455543494161794e49463065434a445a6d637271526a480a663968384778654944556e4c716c646549764e66612b39534169454139554c4254506a6e545568596c653232364f416a67327364686b587462334d75304530460a6e75556d7349513d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2dd9022c7919272d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a4343416979674177494241674955497065567756684e2f71594c67744e4a6c775a484a6a2b49542f7777425159444b3256774d444d784d5441760a42674e56424155544b4464684d445a6c5a5755304d5749334f446c6d4e4467324d3251344e6d49344e7a6334596a46684d6a417859545a6d5a57526b4e5459770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b4449790a4f5463354e574d784e5467305a475a6c59545977596a67795a444d304f546b334d4459304e7a49324d3259344f44526d5a6d4d774b6a414642674d725a5841440a495141565569377856796e4d3835554a366c77566f6d767053654f494236584362766b6f4649667653755a3752714f43415534776767464b4d423847413155640a497751594d42614146486f4737755162654a394959396872683369786f67476d2f7431574d4230474131556444675157424251696c3558425745332b706775430a30306d58426b636d503468502f44414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f77534231444342306142434245414d7476656f414a783476335175394b386749304a326b70376967427634566439420a48454d746c665a4f5164793679444f6c6350725147596a537a7776416a4668384441343662712b78476a33314e46557936706b686f304945514e436b6c2f4b660a6245534c5a364f684563644f6e417a69533567783554714a6d463232794b436a49764c524956784e49685a4e32456e4d41746d3464703145477550424c5572410a747a58686c7a755a754b31785636536b516752415365536f486d6e4e674c686e6e454b54574b7a634c326a7a506a4f41465551544e52792b694f67686c7549330a66696347364e4237634d624c415a6b665631326c696853562b2f37694b33544a3062554e6a51675770615944436745424d4155474179746c63414e42414875360a447475504e4f7572634158632b3431515932336859384b526b42434b434537706873694977526662784b4d4c6c6446474e354f79745166524f5161576f4163760a4957547156394a527a47516147596e6c4c77453d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a434341697967417749424167495545626165377531635037732b472f434462324e75366e505264596b77425159444b3256774d444d784d5441760a42674e56424155544b4449794f5463354e574d784e5467305a475a6c59545977596a67795a444d304f546b334d4459304e7a49324d3259344f44526d5a6d4d770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b4445780a596a59355a57566c5a575131597a4e6d596d497a5a5446695a6a41344d7a5a6d4e6a4d325a5756684e7a4e6b4d5463314f446b774b6a414642674d725a5841440a4951447a676b54523775766f50394e427a5345423967752f6c70642b4e4c33384f5659516c30666569574b582b614f43415534776767464b4d423847413155640a497751594d42614146434b586c6346595466366d43344c54535a63475279592f69452f384d423047413155644467515742425152747037753756772f757a34620a38494e7659323771633946316954414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f775342314443423061424342454262734b67636176452b75793141786b49646c376c4e39696648793348452b4c69750a386c4d453237434d59396b557479772f6c657331483876706d537978684f3461545767777577516137596e39486f4777654548736f304945514862383730484e0a3162556e396e46696831315342416a396c6f6270754a354772492f6d2b673648776d6f517a35556c79306f584d4e6e78454d4137664c327a613031796e4770490a2f757a383272554932764c57536c476b5167524133584667496f56496d6f736441677675504856616f6276334a476a476c332b41444f543163366454366451450a646e4f62524e7564593871687a547666455752346553364f4a746679724f65527958656b324f564a68365944436745424d4155474179746c63414e42414a496a0a7946717764725a435375596d43342b5a555563414e4b514b41314b63524669496c4b63672f7070774b56796b5058624168736e365343567157474137763743650a4c6935684f72482f566c6a41514163645967633d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a43434169796741774942416749555955584d636656334d756370756e6c313933777558784242722f6777425159444b3256774d444d784d5441760a42674e56424155544b444578596a59355a57566c5a575131597a4e6d596d497a5a5446695a6a41344d7a5a6d4e6a4d325a5756684e7a4e6b4d5463314f446b770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b4459780a4e44566a597a63785a6a55334e7a4d795a5463794f574a684e7a6b334e5759334e324d795a54566d4d5441304d57466d5a6a67774b6a414642674d725a5841440a4951424a6f3950677665486a30616876384d6b574851554753785a2f77535464614e4e5a6264425a4e61314c30614f43415534776767464b4d423847413155640a497751594d426141464247326e75377458442b37506876776732396a6275707a3058574a4d423047413155644467515742425268526378783958637935796d360a6558583366433566454547762b44414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f775342314443423061424342454132616e736566305362524e386a37367735687a57352f54435846497351634552730a62534b51594e6e71756731726a4543506e686533412f385a3657477861444b316568452b6e72637643394252677257705536374a6f30494551495a795243484b0a394855692f387936563950305a754e45766d64704564496d51303952552f6c4e507358587879763056456d693657447334654679706d4252394c5658425875640a7243647575767953367442577353366b516752416257525443625872642f716c4c504949383549504238705a3975582b586749484934735348662b33463673650a68412f38307a55427a5369364f7a6330442b496259594259786472585a456b6e386955575364516f6b4b5944436745424d4155474179746c63414e42414b6c4a0a2f335659616c5a6d3958624547544b725652616f43566f55785156483375644d726b39796f716a466f77433465336b6453426c474766386d59454937787673410a6172316b6632624758542f634565464749774d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a4343416979674177494241674955582b69765048544f6d76566b744d6e514759516a754e6c6b2f445577425159444b3256774d444d784d5441760a42674e56424155544b4459784e44566a597a63785a6a55334e7a4d795a5463794f574a684e7a6b334e5759334e324d795a54566d4d5441304d57466d5a6a67770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b44566d0a5a5468685a6a4e6a4e7a526a5a546c685a6a55324e474930597a6c6b4d4445354f4451794d3249345a446b324e475a6a4d7a55774b6a414642674d725a5841440a495143367533626c77453442317864504d65554a50363537502f6d376953742b486572677647626b6b53784d72714f43415534776767464b4d423847413155640a497751594d426141464746467a484831647a4c6e4b627035646664384c6c385151612f344d423047413155644467515742425266364b3838644d3661395753300a7964415a68434f34325754384e54414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f7753423144434230614243424543347a326a754a4978356a4437783649754d4e55693754556f6d577843516639516e0a434a39316f7a586b30764a396e4a4f33526476654a7662765a686f5066445149593854695a7038554b447834652b7a573063486b6f304945514f68704d4a36470a45584c5a67487452416d38316f5858414345462b6e6576324d437636434f6875527446797047394233666f526d32726e46556261565a7330704c66424d4738730a7353524a526361775843696d57344f6b5167524132374667783741343231327170714c61786150643974492b7a70664b57724c59634c7832302b444c6663716e0a4249497055434e3330537541753731736534782f696c634b7561574f4f307144673334534a45774679715944436745424d4155474179746c63414e42414374540a3558727836353971476e79776d6c4b48646c484f364264376650626f797a794951686f4574464e75694433576a44672f56777a38634e43556b552b74684737660a432b575a68637041636b446c6461692b5041633d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a434341697967417749424167495558583044564f796c6745697037727a7679614e4665676344545a4977425159444b3256774d444d784d5441760a42674e56424155544b44566d5a5468685a6a4e6a4e7a526a5a546c685a6a55324e474930597a6c6b4d4445354f4451794d3249345a446b324e475a6a4d7a55770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b44566b0a4e3251774d7a55305a574e684e5467774e4468684f57566c596d4e6c5a6d4d3559544d304e5464684d4463774d7a526b4f5449774b6a414642674d725a5841440a495143696143326748684d4f31706265516255674c48685367464250442f7a584e414777414873573237322b63364f43415534776767464b4d423847413155640a497751594d42614146462f6f727a78307a7072315a4c544a30426d4549376a5a5a5077314d42304741315564446751574242526466514e55374b5741534b6e750a764f2f4a6f30563642774e4e6b6a414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f7753423144434230614243424542595877532f6d7272582b44344d717a4d384a546d49484339584871734a664f47630a623266714259505830555172694c44526c316170484e32327131452b4665614c4857424532755864613151366c596b51416148696f30494551474b483845414e0a4d7631504d4d62577364645a657732472f44522b4139746253693748363830794253653943652b6774616242617251444870673942384c65626d6f50706458740a4154762b6f537a7a6b2b5a7565564b6b516752417a74625532517a614a62634735747745596a59416746757443626e67706732742f32657a3751544e6e344e6d0a723934704f4178384c4970753643662f577a6376642f346b4c4f76577853622f62754d716247767273715944436745424d4155474179746c63414e42414d64780a66466e6b35327275346656354a3167734642746c6879356d4662516e49526947484c78424661544a6b39692b69784f35714662526a71763748512f6a475573490a7372554a513465324a456e534e616b4e6367453d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a434341697967417749424167495541316f576f57507756644335474f3362516f4433726f446531536f77425159444b3256774d444d784d5441760a42674e56424155544b44566b4e3251774d7a55305a574e684e5467774e4468684f57566c596d4e6c5a6d4d3559544d304e5464684d4463774d7a526b4f5449770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b44417a0a4e5745784e6d45784e6a4e6d4d4455315a4442694f5445345a57526b596a51794f44426d4e32466c4f44426b5a5751314d6d45774b6a414642674d725a5841440a495144534e45593167624c4d4e414f432b33656f6b2b5279513666684e384632336f32647836315162734d3054714f43415534776767464b4d423847413155640a497751594d4261414646313941315473705942497165363837386d6a52586f48413032534d42304741315564446751574242514457686168592f4256304c6b590a3764744367506575674e37564b6a414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f7753423144434230614243424541396d5070416d572b49454f58584f4267537933727935334935363244394f5a485a0a2b4447312f4d396d577869556b524131556369714d704767366e6779717033384a354f7055497546736f53564471465650796a786f3049455149472f376831370a416d3737474c6d51316e534d425a6a74724a2b46726d5754635a6a784a3963583043504a7175377775674c3554636a31493863396e424e71736f6b46783870450a74526f71697a377274365a353244326b516752415a7276714664796a347256636a74566b4a624d6c702f386a6d664765614b682f5247363457724b32754e6b390a79684b4f706b695152307035557354616d2b586445767172786a4c61343373723064692f704b45625a715944436745424d4155474179746c63414e42414f75510a71585a553532314c7a4454585878324559715675574379555a494a5a67526c2f4a477332526d5950594a435a756e304b6a3159497658356d425a3370433835770a30664a466d4d3142322b414373702b703651673d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494943656a4343416979674177494241674955533356553735327371445566593630452f684571536e313432415577425159444b3256774d444d784d5441760a42674e56424155544b44417a4e5745784e6d45784e6a4e6d4d4455315a4442694f5445345a57526b596a51794f44426d4e32466c4f44426b5a5751314d6d45770a4942634e4d5467774d7a49794d6a4d314f545535576867504f546b354f5445794d7a45794d7a55354e546c614d444d784d54417642674e56424155544b4452690a4e7a55314e47566d4f575268593245344d7a55785a6a597a595751774e475a6c4d544579595452684e3251334f4751344d4455774b6a414642674d725a5841440a4951422f6f475854363775635978396c7078465a4652597674676d437942483232692f4c6e554e304b46364c73614f43415534776767464b4d423847413155640a497751594d42614146414e614671466a3846585175526a7432304b4139363641337455714d42304741315564446751574242524c645654766e61796f4e52396a0a7251542b4553704b66586a594254414f42674e56485138424166384542414d434167517744775944565230544151482f42415577417745422f7a43423567594b0a4b7759424241485765514942474145422f77534231444342306142434245444a3657313561697079433255764d6971364943322f77466b7673466339504f72540a314e6e675a47666b65384a6c6e4f37385652555a6373463775687471797265796a697135695a485339684d304a3576494f78756a6f304945514a635a3738616c0a6e43744a69577143486a5467475a6a6f572b6c514a6a4a3955783530545478526545703365454f44394f3374347967645348347254467569754c3674646c5a380a72432f304b544334473576456f77476b51675241675150494251656d5a316973516f4635724b70506f747048584e38595978475935574651497a6b39647a37500a7a78496e5131716e4741736a51505353392b4a4d79774444416937584b754677526630576b32543954615944436745424d4155474179746c63414e424146556c0a5572545135717043634266504765546163584e776c35793357544667706a464b722b4d77367175736a2b62645a366c2b4e334378764f784a396d2b6939364d780a727054366b69536e417a6b2b327a67536941343d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d + // {"ev-triples":{"identity-triples":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Inc","model":"ACME RoadRunner","layer":1}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"},{"type":"pkix-base64-cert","value":"-----BEGIN CERTIFICATE-----\nMIIB4TCCAYegAwIBAgIUGhrA9M3yQIFqckA2v6nQewmF30IwCgYIKoZIzj0EAwIw\nRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\ndGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzA5MDQxMTAxNDhaGA8yMDUxMDEx\nOTExMDE0OFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAf\nBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqG\nSM49AwEHA0IABFtQb6hfv68vAVmu2RDFNcWGBxEPAZS0+DBRzmjviAk7SL6xHhKa\n/7H0wD+uh9oQh3W+RxFyrUSqHekC3DizwNWjUzBRMB0GA1UdDgQWBBQWpNPb6eWD\nSM/+jwpbzoO3iHg4LTAfBgNVHSMEGDAWgBQWpNPb6eWDSM/+jwpbzoO3iHg4LTAP\nBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIAayNIF0eCJDZmcrqRjH\nf9h8GxeIDUnLqldeIvNfa+9SAiEA9ULBTPjnTUhYle226OAjg2sdhkXtb3Mu0E0F\nnuUmsIQ=\n-----END CERTIFICATE-----"},{"type":"pkix-base64-cert-path","value":"-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUIpeVwVhN/qYLgtNJlwZHJj+IT/wwBQYDK2VwMDMxMTAv\nBgNVBAUTKDdhMDZlZWU0MWI3ODlmNDg2M2Q4NmI4Nzc4YjFhMjAxYTZmZWRkNTYw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDIy\nOTc5NWMxNTg0ZGZlYTYwYjgyZDM0OTk3MDY0NzI2M2Y4ODRmZmMwKjAFBgMrZXAD\nIQAVUi7xVynM85UJ6lwVomvpSeOIB6XCbvkoFIfvSuZ7RqOCAU4wggFKMB8GA1Ud\nIwQYMBaAFHoG7uQbeJ9IY9hrh3ixogGm/t1WMB0GA1UdDgQWBBQil5XBWE3+pguC\n00mXBkcmP4hP/DAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEAMtveoAJx4v3Qu9K8gI0J2kp7igBv4Vd9B\nHEMtlfZOQdy6yDOlcPrQGYjSzwvAjFh8DA46bq+xGj31NFUy6pkho0IEQNCkl/Kf\nbESLZ6OhEcdOnAziS5gx5TqJmF22yKCjIvLRIVxNIhZN2EnMAtm4dp1EGuPBLUrA\ntzXhlzuZuK1xV6SkQgRASeSoHmnNgLhnnEKTWKzcL2jzPjOAFUQTNRy+iOghluI3\nficG6NB7cMbLAZkfV12lihSV+/7iK3TJ0bUNjQgWpaYDCgEBMAUGAytlcANBAHu6\nDtuPNOurcAXc+41QY23hY8KRkBCKCE7phsiIwRfbxKMLldFGN5OytQfROQaWoAcv\nIWTqV9JRzGQaGYnlLwE=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUEbae7u1cP7s+G/CDb2Nu6nPRdYkwBQYDK2VwMDMxMTAv\nBgNVBAUTKDIyOTc5NWMxNTg0ZGZlYTYwYjgyZDM0OTk3MDY0NzI2M2Y4ODRmZmMw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDEx\nYjY5ZWVlZWQ1YzNmYmIzZTFiZjA4MzZmNjM2ZWVhNzNkMTc1ODkwKjAFBgMrZXAD\nIQDzgkTR7uvoP9NBzSEB9gu/lpd+NL38OVYQl0feiWKX+aOCAU4wggFKMB8GA1Ud\nIwQYMBaAFCKXlcFYTf6mC4LTSZcGRyY/iE/8MB0GA1UdDgQWBBQRtp7u7Vw/uz4b\n8INvY27qc9F1iTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEBbsKgcavE+uy1AxkIdl7lN9ifHy3HE+Liu\n8lME27CMY9kUtyw/les1H8vpmSyxhO4aTWgwuwQa7Yn9HoGweEHso0IEQHb870HN\n1bUn9nFih11SBAj9lobpuJ5GrI/m+g6HwmoQz5Uly0oXMNnxEMA7fL2za01ynGpI\n/uz82rUI2vLWSlGkQgRA3XFgIoVImosdAgvuPHVaobv3JGjGl3+ADOT1c6dT6dQE\ndnObRNudY8qhzTvfEWR4eS6OJtfyrOeRyXek2OVJh6YDCgEBMAUGAytlcANBAJIj\nyFqwdrZCSuYmC4+ZUUcANKQKA1KcRFiIlKcg/ppwKVykPXbAhsn6SCVqWGA7v7Ce\nLi5hOrH/VljAQAcdYgc=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUYUXMcfV3Mucpunl193wuXxBBr/gwBQYDK2VwMDMxMTAv\nBgNVBAUTKDExYjY5ZWVlZWQ1YzNmYmIzZTFiZjA4MzZmNjM2ZWVhNzNkMTc1ODkw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDYx\nNDVjYzcxZjU3NzMyZTcyOWJhNzk3NWY3N2MyZTVmMTA0MWFmZjgwKjAFBgMrZXAD\nIQBJo9PgveHj0ahv8MkWHQUGSxZ/wSTdaNNZbdBZNa1L0aOCAU4wggFKMB8GA1Ud\nIwQYMBaAFBG2nu7tXD+7Phvwg29jbupz0XWJMB0GA1UdDgQWBBRhRcxx9Xcy5ym6\neXX3fC5fEEGv+DAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEA2ansef0SbRN8j76w5hzW5/TCXFIsQcERs\nbSKQYNnqug1rjECPnhe3A/8Z6WGxaDK1ehE+nrcvC9BRgrWpU67Jo0IEQIZyRCHK\n9HUi/8y6V9P0ZuNEvmdpEdImQ09RU/lNPsXXxyv0VEmi6WDs4eFypmBR9LVXBXud\nrCduuvyS6tBWsS6kQgRAbWRTCbXrd/qlLPII85IPB8pZ9uX+XgIHI4sSHf+3F6se\nhA/80zUBzSi6Ozc0D+IbYYBYxdrXZEkn8iUWSdQokKYDCgEBMAUGAytlcANBAKlJ\n/3VYalZm9XbEGTKrVRaoCVoUxQVH3udMrk9yoqjFowC4e3kdSBlGGf8mYEI7xvsA\nar1kf2bGXT/cEeFGIwM=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUX+ivPHTOmvVktMnQGYQjuNlk/DUwBQYDK2VwMDMxMTAv\nBgNVBAUTKDYxNDVjYzcxZjU3NzMyZTcyOWJhNzk3NWY3N2MyZTVmMTA0MWFmZjgw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDVm\nZThhZjNjNzRjZTlhZjU2NGI0YzlkMDE5ODQyM2I4ZDk2NGZjMzUwKjAFBgMrZXAD\nIQC6u3blwE4B1xdPMeUJP657P/m7iSt+HergvGbkkSxMrqOCAU4wggFKMB8GA1Ud\nIwQYMBaAFGFFzHH1dzLnKbp5dfd8Ll8QQa/4MB0GA1UdDgQWBBRf6K88dM6a9WS0\nydAZhCO42WT8NTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEC4z2juJIx5jD7x6IuMNUi7TUomWxCQf9Qn\nCJ91ozXk0vJ9nJO3RdveJvbvZhoPfDQIY8TiZp8UKDx4e+zW0cHko0IEQOhpMJ6G\nEXLZgHtRAm81oXXACEF+nev2MCv6COhuRtFypG9B3foRm2rnFUbaVZs0pLfBMG8s\nsSRJRcawXCimW4OkQgRA27Fgx7A4212qpqLaxaPd9tI+zpfKWrLYcLx20+DLfcqn\nBIIpUCN30SuAu71se4x/ilcKuaWOO0qDg34SJEwFyqYDCgEBMAUGAytlcANBACtT\n5Xrx659qGnywmlKHdlHO6Bd7fPboyzyIQhoEtFNuiD3WjDg/Vwz8cNCUkU+thG7f\nC+WZhcpAckDldai+PAc=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUXX0DVOylgEip7rzvyaNFegcDTZIwBQYDK2VwMDMxMTAv\nBgNVBAUTKDVmZThhZjNjNzRjZTlhZjU2NGI0YzlkMDE5ODQyM2I4ZDk2NGZjMzUw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDVk\nN2QwMzU0ZWNhNTgwNDhhOWVlYmNlZmM5YTM0NTdhMDcwMzRkOTIwKjAFBgMrZXAD\nIQCiaC2gHhMO1pbeQbUgLHhSgFBPD/zXNAGwAHsW272+c6OCAU4wggFKMB8GA1Ud\nIwQYMBaAFF/orzx0zpr1ZLTJ0BmEI7jZZPw1MB0GA1UdDgQWBBRdfQNU7KWASKnu\nvO/Jo0V6BwNNkjAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEBYXwS/mrrX+D4MqzM8JTmIHC9XHqsJfOGc\nb2fqBYPX0UQriLDRl1apHN22q1E+FeaLHWBE2uXda1Q6lYkQAaHio0IEQGKH8EAN\nMv1PMMbWsddZew2G/DR+A9tbSi7H680yBSe9Ce+gtabBarQDHpg9B8LebmoPpdXt\nATv+oSzzk+ZueVKkQgRAztbU2QzaJbcG5twEYjYAgFutCbngpg2t/2ez7QTNn4Nm\nr94pOAx8LIpu6Cf/Wzcvd/4kLOvWxSb/buMqbGvrsqYDCgEBMAUGAytlcANBAMdx\nfFnk52ru4fV5J1gsFBtlhy5mFbQnIRiGHLxBFaTJk9i+ixO5qFbRjqv7HQ/jGUsI\nsrUJQ4e2JEnSNakNcgE=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUA1oWoWPwVdC5GO3bQoD3roDe1SowBQYDK2VwMDMxMTAv\nBgNVBAUTKDVkN2QwMzU0ZWNhNTgwNDhhOWVlYmNlZmM5YTM0NTdhMDcwMzRkOTIw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDAz\nNWExNmExNjNmMDU1ZDBiOTE4ZWRkYjQyODBmN2FlODBkZWQ1MmEwKjAFBgMrZXAD\nIQDSNEY1gbLMNAOC+3eok+RyQ6fhN8F23o2dx61QbsM0TqOCAU4wggFKMB8GA1Ud\nIwQYMBaAFF19A1TspYBIqe6878mjRXoHA02SMB0GA1UdDgQWBBQDWhahY/BV0LkY\n7dtCgPeugN7VKjAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEA9mPpAmW+IEOXXOBgSy3ry53I562D9OZHZ\n+DG1/M9mWxiUkRA1UciqMpGg6ngyqp38J5OpUIuFsoSVDqFVPyjxo0IEQIG/7h17\nAm77GLmQ1nSMBZjtrJ+FrmWTcZjxJ9cX0CPJqu7wugL5Tcj1I8c9nBNqsokFx8pE\ntRoqiz7rt6Z52D2kQgRAZrvqFdyj4rVcjtVkJbMlp/8jmfGeaKh/RG64WrK2uNk9\nyhKOpkiQR0p5UsTam+XdEvqrxjLa43sr0di/pKEbZqYDCgEBMAUGAytlcANBAOuQ\nqXZU521LzDTXXx2EYqVuWCyUZIJZgRl/JGs2RmYPYJCZun0Kj1YIvX5mBZ3pC85w\n0fJFmM1B2+ACsp+p6Qg=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUS3VU752sqDUfY60E/hEqSn142AUwBQYDK2VwMDMxMTAv\nBgNVBAUTKDAzNWExNmExNjNmMDU1ZDBiOTE4ZWRkYjQyODBmN2FlODBkZWQ1MmEw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDRi\nNzU1NGVmOWRhY2E4MzUxZjYzYWQwNGZlMTEyYTRhN2Q3OGQ4MDUwKjAFBgMrZXAD\nIQB/oGXT67ucYx9lpxFZFRYvtgmCyBH22i/LnUN0KF6LsaOCAU4wggFKMB8GA1Ud\nIwQYMBaAFANaFqFj8FXQuRjt20KA966A3tUqMB0GA1UdDgQWBBRLdVTvnayoNR9j\nrQT+ESpKfXjYBTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEDJ6W15aipyC2UvMiq6IC2/wFkvsFc9POrT\n1NngZGfke8JlnO78VRUZcsF7uhtqyreyjiq5iZHS9hM0J5vIOxujo0IEQJcZ78al\nnCtJiWqCHjTgGZjoW+lQJjJ9Ux50TTxReEp3eEOD9O3t4ygdSH4rTFuiuL6tdlZ8\nrC/0KTC4G5vEowGkQgRAgQPIBQemZ1isQoF5rKpPotpHXN8YYxGY5WFQIzk9dz7P\nzxInQ1qnGAsjQPSS9+JMywDDAi7XKuFwRf0Wk2T9TaYDCgEBMAUGAytlcANBAFUl\nUrTQ5qpCcBfPGeTacXNwl5y3WTFgpjFKr+Mw6qusj+bdZ6l+N3CxvOxJ9m+i96Mx\nrpT6kiSnAzk+2zgSiA4=\n-----END CERTIFICATE-----"}]}]}} + +} + +func Example_encode_CoSWIDTriples() { + coev := NewConciseEvidence() + err := coev.AddTriples(NewEvTriples().AddCoSWIDTriple( + &CoSWIDTriple{ + Environment: comid.Environment{ + Instance: comid.MustNewUEIDInstance(comid.TestUEID), + }, + Evidence: *NewCoSWIDEvidence().AddCoSWIDEvidenceMap( + &CoSWIDEvidenceMap{ + TagID: swid.NewTagID(TestTag), + Evidence: swid.Evidence{Date: TestDate, DeviceID: TestDeviceID}, + }, + ), + }, + )) + + if err != nil { + log.Fatalf("could not get new concise evidence: %v", err) + } + + cbor, err := coev.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } + + json, err := coev.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } + + // Output: + // a100a1048182a101d902264702deadbeefdead81a200500001000100010001000100010001000101a21823c1001824782442414438303942312d373033322d343344392d384639342d424631323845354430363144 + // {"ev-triples":{"coswid-triples":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"coswid-evidence":[{"tagId":"00010001-0001-0001-0001-000100010001","evidence":{"date":"1970-01-01T00:00:00Z","device-id":"BAD809B1-7032-43D9-8F94-BF128E5D061D"}}]}]}} +} + +func Example_encode_AttestKeyTriples() { + coev := NewConciseEvidence() + err := coev.AddTriples(NewEvTriples().AddAttestKeyTriple( + &comid.KeyTriple{ + Environment: comid.Environment{ + Instance: comid.MustNewUEIDInstance(comid.TestUEID), + }, + VerifKeys: *comid.NewCryptoKeys(). + Add( + comid.MustNewPKIXBase64Key(comid.TestECPubKey), + ), + }, + ), + ) + if err != nil { + log.Fatalf("could not get new concise evidence: %v", err) + } + + cbor, err := coev.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } + + json, err := coev.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } + + // Output: + // a100a1058182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + // {"ev-triples":{"attestkey-triples":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} + +} + +func Example_encode_TaggedConciseEvidence() { + coev := NewConciseEvidence() + err := coev.AddTriples(NewEvTriples().AddAttestKeyTriple( + &comid.KeyTriple{ + Environment: comid.Environment{ + Instance: comid.MustNewUEIDInstance(comid.TestUEID), + }, + VerifKeys: *comid.NewCryptoKeys(). + Add( + comid.MustNewPKIXBase64Key(comid.TestECPubKey), + ), + }, + ), + ) + if err != nil { + log.Fatalf("could not get new concise evidence: %v", err) + } + te, _ := NewTaggedConciseEvidence(coev) + cbor, err := te.ToCBOR() + if err == nil { + fmt.Printf("%x\n", cbor) + } + + json, err := te.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) + } + // Output: + // d9023ba100a1058182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d + // {"ev-triples":{"attestkey-triples":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}]}} +} + +// This Example_decode_JSON has the complete container +func Example_decode_JSON() { + j := ` +{ + "ev-triples": { + "evidence-triples": [ + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.5.2.8192" + }, + "vendor": "ACME Ltd.", + "model": "RoadRunner", + "layer": 0, + "index": 1 + } + }, + "measurements": [ + { + "key": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "value": { + "svn": { + "type": "exact-value", + "value": 2 + }, + "digests": [ + "sha-256-32;q83vAA==", + "sha-256-32;/////w==" + ], + "flags": { + "is-secure": false, + "is-debug": true + }, + "raw-value": { + "type": "bytes", + "value": "AQIDBA==" + }, + "raw-value-mask": "/////w==", + "mac-addr": "02:00:5e:10:00:00:00:01", + "ip-addr": "2001:db8::68", + "serial-number": "C02X70VHJHD5", + "ueid": "At6tvu/erQ==", + "uuid": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + } + } + ] + }, + { + "environment": { + "class": { + "id": { + "type": "oid", + "value": "2.5.2.8192" + }, + "vendor": "ACME Ltd.", + "model": "ACME Public", + "layer": 1, + "index": 2 + } + }, + "measurements": [ + { + "key": { + "type": "uint", + "value": 1 + }, + "value": { + "svn": { + "type": "exact-value", + "value": 2 + }, + "digests": [ + "sha-256-32;q83vAA==", + "sha-256-32;/////w==" + ], + "flags": { + "is-secure": false, + "is-debug": true + }, + "raw-value": { + "type": "bytes", + "value": "AQIDBA==" + }, + "raw-value-mask": "/////w==", + "mac-addr": "02:00:5e:10:00:00:00:02" + }, + "authorized-by": { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----" + } + } + ] + } + ], + "identity-triples": [ + { + "environment": { + "class": { + "id": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "vendor": "ACME Inc", + "model": "ACME RoadRunner", + "layer": 1 + } + }, + "verification-keys": [ + { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" + }, + { + "type": "pkix-base64-cert", + "value": "-----BEGIN CERTIFICATE-----\nMIIB4TCCAYegAwIBAgIUGhrA9M3yQIFqckA2v6nQewmF30IwCgYIKoZIzj0EAwIw\nRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\ndGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzA5MDQxMTAxNDhaGA8yMDUxMDEx\nOTExMDE0OFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAf\nBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqG\nSM49AwEHA0IABFtQb6hfv68vAVmu2RDFNcWGBxEPAZS0+DBRzmjviAk7SL6xHhKa\n/7H0wD+uh9oQh3W+RxFyrUSqHekC3DizwNWjUzBRMB0GA1UdDgQWBBQWpNPb6eWD\nSM/+jwpbzoO3iHg4LTAfBgNVHSMEGDAWgBQWpNPb6eWDSM/+jwpbzoO3iHg4LTAP\nBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIAayNIF0eCJDZmcrqRjH\nf9h8GxeIDUnLqldeIvNfa+9SAiEA9ULBTPjnTUhYle226OAjg2sdhkXtb3Mu0E0F\nnuUmsIQ=\n-----END CERTIFICATE-----" + }, + { + "type": "pkix-base64-cert-path", + "value": "-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUIpeVwVhN/qYLgtNJlwZHJj+IT/wwBQYDK2VwMDMxMTAv\nBgNVBAUTKDdhMDZlZWU0MWI3ODlmNDg2M2Q4NmI4Nzc4YjFhMjAxYTZmZWRkNTYw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDIy\nOTc5NWMxNTg0ZGZlYTYwYjgyZDM0OTk3MDY0NzI2M2Y4ODRmZmMwKjAFBgMrZXAD\nIQAVUi7xVynM85UJ6lwVomvpSeOIB6XCbvkoFIfvSuZ7RqOCAU4wggFKMB8GA1Ud\nIwQYMBaAFHoG7uQbeJ9IY9hrh3ixogGm/t1WMB0GA1UdDgQWBBQil5XBWE3+pguC\n00mXBkcmP4hP/DAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEAMtveoAJx4v3Qu9K8gI0J2kp7igBv4Vd9B\nHEMtlfZOQdy6yDOlcPrQGYjSzwvAjFh8DA46bq+xGj31NFUy6pkho0IEQNCkl/Kf\nbESLZ6OhEcdOnAziS5gx5TqJmF22yKCjIvLRIVxNIhZN2EnMAtm4dp1EGuPBLUrA\ntzXhlzuZuK1xV6SkQgRASeSoHmnNgLhnnEKTWKzcL2jzPjOAFUQTNRy+iOghluI3\nficG6NB7cMbLAZkfV12lihSV+/7iK3TJ0bUNjQgWpaYDCgEBMAUGAytlcANBAHu6\nDtuPNOurcAXc+41QY23hY8KRkBCKCE7phsiIwRfbxKMLldFGN5OytQfROQaWoAcv\nIWTqV9JRzGQaGYnlLwE=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUEbae7u1cP7s+G/CDb2Nu6nPRdYkwBQYDK2VwMDMxMTAv\nBgNVBAUTKDIyOTc5NWMxNTg0ZGZlYTYwYjgyZDM0OTk3MDY0NzI2M2Y4ODRmZmMw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDEx\nYjY5ZWVlZWQ1YzNmYmIzZTFiZjA4MzZmNjM2ZWVhNzNkMTc1ODkwKjAFBgMrZXAD\nIQDzgkTR7uvoP9NBzSEB9gu/lpd+NL38OVYQl0feiWKX+aOCAU4wggFKMB8GA1Ud\nIwQYMBaAFCKXlcFYTf6mC4LTSZcGRyY/iE/8MB0GA1UdDgQWBBQRtp7u7Vw/uz4b\n8INvY27qc9F1iTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEBbsKgcavE+uy1AxkIdl7lN9ifHy3HE+Liu\n8lME27CMY9kUtyw/les1H8vpmSyxhO4aTWgwuwQa7Yn9HoGweEHso0IEQHb870HN\n1bUn9nFih11SBAj9lobpuJ5GrI/m+g6HwmoQz5Uly0oXMNnxEMA7fL2za01ynGpI\n/uz82rUI2vLWSlGkQgRA3XFgIoVImosdAgvuPHVaobv3JGjGl3+ADOT1c6dT6dQE\ndnObRNudY8qhzTvfEWR4eS6OJtfyrOeRyXek2OVJh6YDCgEBMAUGAytlcANBAJIj\nyFqwdrZCSuYmC4+ZUUcANKQKA1KcRFiIlKcg/ppwKVykPXbAhsn6SCVqWGA7v7Ce\nLi5hOrH/VljAQAcdYgc=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUYUXMcfV3Mucpunl193wuXxBBr/gwBQYDK2VwMDMxMTAv\nBgNVBAUTKDExYjY5ZWVlZWQ1YzNmYmIzZTFiZjA4MzZmNjM2ZWVhNzNkMTc1ODkw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDYx\nNDVjYzcxZjU3NzMyZTcyOWJhNzk3NWY3N2MyZTVmMTA0MWFmZjgwKjAFBgMrZXAD\nIQBJo9PgveHj0ahv8MkWHQUGSxZ/wSTdaNNZbdBZNa1L0aOCAU4wggFKMB8GA1Ud\nIwQYMBaAFBG2nu7tXD+7Phvwg29jbupz0XWJMB0GA1UdDgQWBBRhRcxx9Xcy5ym6\neXX3fC5fEEGv+DAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEA2ansef0SbRN8j76w5hzW5/TCXFIsQcERs\nbSKQYNnqug1rjECPnhe3A/8Z6WGxaDK1ehE+nrcvC9BRgrWpU67Jo0IEQIZyRCHK\n9HUi/8y6V9P0ZuNEvmdpEdImQ09RU/lNPsXXxyv0VEmi6WDs4eFypmBR9LVXBXud\nrCduuvyS6tBWsS6kQgRAbWRTCbXrd/qlLPII85IPB8pZ9uX+XgIHI4sSHf+3F6se\nhA/80zUBzSi6Ozc0D+IbYYBYxdrXZEkn8iUWSdQokKYDCgEBMAUGAytlcANBAKlJ\n/3VYalZm9XbEGTKrVRaoCVoUxQVH3udMrk9yoqjFowC4e3kdSBlGGf8mYEI7xvsA\nar1kf2bGXT/cEeFGIwM=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUX+ivPHTOmvVktMnQGYQjuNlk/DUwBQYDK2VwMDMxMTAv\nBgNVBAUTKDYxNDVjYzcxZjU3NzMyZTcyOWJhNzk3NWY3N2MyZTVmMTA0MWFmZjgw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDVm\nZThhZjNjNzRjZTlhZjU2NGI0YzlkMDE5ODQyM2I4ZDk2NGZjMzUwKjAFBgMrZXAD\nIQC6u3blwE4B1xdPMeUJP657P/m7iSt+HergvGbkkSxMrqOCAU4wggFKMB8GA1Ud\nIwQYMBaAFGFFzHH1dzLnKbp5dfd8Ll8QQa/4MB0GA1UdDgQWBBRf6K88dM6a9WS0\nydAZhCO42WT8NTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEC4z2juJIx5jD7x6IuMNUi7TUomWxCQf9Qn\nCJ91ozXk0vJ9nJO3RdveJvbvZhoPfDQIY8TiZp8UKDx4e+zW0cHko0IEQOhpMJ6G\nEXLZgHtRAm81oXXACEF+nev2MCv6COhuRtFypG9B3foRm2rnFUbaVZs0pLfBMG8s\nsSRJRcawXCimW4OkQgRA27Fgx7A4212qpqLaxaPd9tI+zpfKWrLYcLx20+DLfcqn\nBIIpUCN30SuAu71se4x/ilcKuaWOO0qDg34SJEwFyqYDCgEBMAUGAytlcANBACtT\n5Xrx659qGnywmlKHdlHO6Bd7fPboyzyIQhoEtFNuiD3WjDg/Vwz8cNCUkU+thG7f\nC+WZhcpAckDldai+PAc=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUXX0DVOylgEip7rzvyaNFegcDTZIwBQYDK2VwMDMxMTAv\nBgNVBAUTKDVmZThhZjNjNzRjZTlhZjU2NGI0YzlkMDE5ODQyM2I4ZDk2NGZjMzUw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDVk\nN2QwMzU0ZWNhNTgwNDhhOWVlYmNlZmM5YTM0NTdhMDcwMzRkOTIwKjAFBgMrZXAD\nIQCiaC2gHhMO1pbeQbUgLHhSgFBPD/zXNAGwAHsW272+c6OCAU4wggFKMB8GA1Ud\nIwQYMBaAFF/orzx0zpr1ZLTJ0BmEI7jZZPw1MB0GA1UdDgQWBBRdfQNU7KWASKnu\nvO/Jo0V6BwNNkjAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEBYXwS/mrrX+D4MqzM8JTmIHC9XHqsJfOGc\nb2fqBYPX0UQriLDRl1apHN22q1E+FeaLHWBE2uXda1Q6lYkQAaHio0IEQGKH8EAN\nMv1PMMbWsddZew2G/DR+A9tbSi7H680yBSe9Ce+gtabBarQDHpg9B8LebmoPpdXt\nATv+oSzzk+ZueVKkQgRAztbU2QzaJbcG5twEYjYAgFutCbngpg2t/2ez7QTNn4Nm\nr94pOAx8LIpu6Cf/Wzcvd/4kLOvWxSb/buMqbGvrsqYDCgEBMAUGAytlcANBAMdx\nfFnk52ru4fV5J1gsFBtlhy5mFbQnIRiGHLxBFaTJk9i+ixO5qFbRjqv7HQ/jGUsI\nsrUJQ4e2JEnSNakNcgE=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUA1oWoWPwVdC5GO3bQoD3roDe1SowBQYDK2VwMDMxMTAv\nBgNVBAUTKDVkN2QwMzU0ZWNhNTgwNDhhOWVlYmNlZmM5YTM0NTdhMDcwMzRkOTIw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDAz\nNWExNmExNjNmMDU1ZDBiOTE4ZWRkYjQyODBmN2FlODBkZWQ1MmEwKjAFBgMrZXAD\nIQDSNEY1gbLMNAOC+3eok+RyQ6fhN8F23o2dx61QbsM0TqOCAU4wggFKMB8GA1Ud\nIwQYMBaAFF19A1TspYBIqe6878mjRXoHA02SMB0GA1UdDgQWBBQDWhahY/BV0LkY\n7dtCgPeugN7VKjAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEA9mPpAmW+IEOXXOBgSy3ry53I562D9OZHZ\n+DG1/M9mWxiUkRA1UciqMpGg6ngyqp38J5OpUIuFsoSVDqFVPyjxo0IEQIG/7h17\nAm77GLmQ1nSMBZjtrJ+FrmWTcZjxJ9cX0CPJqu7wugL5Tcj1I8c9nBNqsokFx8pE\ntRoqiz7rt6Z52D2kQgRAZrvqFdyj4rVcjtVkJbMlp/8jmfGeaKh/RG64WrK2uNk9\nyhKOpkiQR0p5UsTam+XdEvqrxjLa43sr0di/pKEbZqYDCgEBMAUGAytlcANBAOuQ\nqXZU521LzDTXXx2EYqVuWCyUZIJZgRl/JGs2RmYPYJCZun0Kj1YIvX5mBZ3pC85w\n0fJFmM1B2+ACsp+p6Qg=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICejCCAiygAwIBAgIUS3VU752sqDUfY60E/hEqSn142AUwBQYDK2VwMDMxMTAv\nBgNVBAUTKDAzNWExNmExNjNmMDU1ZDBiOTE4ZWRkYjQyODBmN2FlODBkZWQ1MmEw\nIBcNMTgwMzIyMjM1OTU5WhgPOTk5OTEyMzEyMzU5NTlaMDMxMTAvBgNVBAUTKDRi\nNzU1NGVmOWRhY2E4MzUxZjYzYWQwNGZlMTEyYTRhN2Q3OGQ4MDUwKjAFBgMrZXAD\nIQB/oGXT67ucYx9lpxFZFRYvtgmCyBH22i/LnUN0KF6LsaOCAU4wggFKMB8GA1Ud\nIwQYMBaAFANaFqFj8FXQuRjt20KA966A3tUqMB0GA1UdDgQWBBRLdVTvnayoNR9j\nrQT+ESpKfXjYBTAOBgNVHQ8BAf8EBAMCAgQwDwYDVR0TAQH/BAUwAwEB/zCB5gYK\nKwYBBAHWeQIBGAEB/wSB1DCB0aBCBEDJ6W15aipyC2UvMiq6IC2/wFkvsFc9POrT\n1NngZGfke8JlnO78VRUZcsF7uhtqyreyjiq5iZHS9hM0J5vIOxujo0IEQJcZ78al\nnCtJiWqCHjTgGZjoW+lQJjJ9Ux50TTxReEp3eEOD9O3t4ygdSH4rTFuiuL6tdlZ8\nrC/0KTC4G5vEowGkQgRAgQPIBQemZ1isQoF5rKpPotpHXN8YYxGY5WFQIzk9dz7P\nzxInQ1qnGAsjQPSS9+JMywDDAi7XKuFwRf0Wk2T9TaYDCgEBMAUGAytlcANBAFUl\nUrTQ5qpCcBfPGeTacXNwl5y3WTFgpjFKr+Mw6qusj+bdZ6l+N3CxvOxJ9m+i96Mx\nrpT6kiSnAzk+2zgSiA4=\n-----END CERTIFICATE-----" + } + ] + } + ], + "coswid-triples": [ + { + "environment": { + "instance": { + "type": "ueid", + "value": "At6tvu/erQ==" + } + }, + "coswid-evidence": [ + { + "tagId": "00010001-0001-0001-0001-000100010001", + "evidence": { + "date": "1970-01-01T00:00:00Z", + "device-id": "BAD809B1-7032-43D9-8F94-BF128E5D061D" + } + } + ] + } + ], + "attestkey-triples": [ + { + "environment": { + "instance": { + "type": "ueid", + "value": "At6tvu/erQ==" + } + }, + "verification-keys": [ + { + "type": "pkix-base64-key", + "value": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----" + } + ] + } + ], + "evidence-id": { + "type": "uuid", + "value": "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + }, + "profile": "http://example.com/example-profile" + } +} +` + coev := NewConciseEvidence() + err := coev.FromJSON([]byte(j)) + if err != nil { + fmt.Printf("FAIL: %v", err) + } else { + fmt.Println("OK") + } + // Output: OK +} + +var ( + //go:embed testcases/ce-evidence.cbor + testCEEvidence []byte + + //go:embed testcases/ce-identity.cbor + testCEIdentity []byte + + //go:embed testcases/ce-coswid.cbor + testCECoSWID []byte +) + +func TestExample_decode_CBOR(_ *testing.T) { + tvs := []struct { + descr string + inp []byte + }{ + { + descr: "Test with Concise Evidence Evidence Triple Diag", + inp: testCEEvidence, + }, + + { + descr: "Test with Concise Evidence Identity Triple Diag", + inp: testCEIdentity, + }, + + { + descr: "Test with Concise Evidence CoSWID Triple Diag", + inp: testCECoSWID, + }, + } + for _, tv := range tvs { + te := &TaggedConciseEvidence{} + err := te.FromCBOR(tv.inp) + if err != nil { + fmt.Printf("FAIL: %v", err) + } else { + fmt.Println("OK") + } + // Output: OK + } +} diff --git a/coev/extensions.go b/coev/extensions.go new file mode 100644 index 00000000..1447bf89 --- /dev/null +++ b/coev/extensions.go @@ -0,0 +1,17 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import "github.com/veraison/corim/extensions" + +const ( + ExtConciseEvidence extensions.Point = "ConciseEvidence" + ExtEvTriples extensions.Point = "EvTriples" + ExtEvidenceTriples extensions.Point = "EvidenceTriples" + ExtEvidenceTriplesFlags extensions.Point = "EvidenceTriplesFlags" +) + +type Extensions struct { + extensions.Extensions +} diff --git a/coev/test_vars.go b/coev/test_vars.go new file mode 100644 index 00000000..78699b23 --- /dev/null +++ b/coev/test_vars.go @@ -0,0 +1,22 @@ +// Copyright 2025 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package coev + +import ( + "time" + + "github.com/google/uuid" + "github.com/veraison/corim/comid" +) + +//nolint:lll +var ( + TestUUIDString = "31fb5abf-023e-4992-aa4e-95f9c1503bfa" + TestUUID = comid.UUID(uuid.Must(uuid.Parse(TestUUIDString))) + TestProfile = "https://abc.com" + TestTag = "00010001-0001-0001-0001-000100010001" + TestDeviceID = "BAD809B1-7032-43D9-8F94-BF128E5D061D" + TestKey = true + TestDate, _ = time.Parse(time.RFC3339, "1970-01-01T00:00:00Z") +) diff --git a/coev/testcases/ce-coswid.cbor b/coev/testcases/ce-coswid.cbor new file mode 100644 index 0000000000000000000000000000000000000000..5d7898013dfdc8fdce1295e914bd01b6a36098f6 GIT binary patch literal 194 zcmcb~WWA7KAxmS^LWV^QH}XB%*aNE>`B{=V81pJBt1|PF^-?Pma|?1(8y7QFC?x0S z>Ln)Urs@?Hr5G6)8tW#f>J}Fm>Xw=48tR(r8Za&tSS%rt1(Z!KNlh-vFOrbVRV^sW zPcKT$O-U?CRF#lgCy2p~&Rb^)%t077UZNhE@E8B z)Y!xrp|DuwkM6PLbyeBf$0|yvuoh2yw)t!3*?_;Xw|%8|{ngH5y2+$fu|XFEoLt>K z{S*R1oqRl<6}(*|!Gc`AZrSCo<&hrV`B9!#Sq9FIfsvk2wu@t6vP)pPqpMqlS@T@ rn0cPIe`;!~L8fP6cDS*JZ&Zk}Ye;5ZrdLY3tt}VW39f!F=&k|)SLj=f literal 0 HcmV?d00001 diff --git a/coev/testcases/ce-identity.cbor b/coev/testcases/ce-identity.cbor new file mode 100644 index 0000000000000000000000000000000000000000..093e40c22f5fb7c50a448a239ea7fc9720b19cb5 GIT binary patch literal 1002 zcmbV~J#X7q6ozdTT{su$P@wSA3>6&Fl1xh|0`%Mu^4^M<5-DEMQiehjDN>)5WJx5o z)fqEo&SWG*(GEen^e41H^8>1({~}YS$chmdLD8Xidha<02hVx&?sDJV9NirMbbWL5 z^62kx8sTqGC*?Qa-#q*B*T@E52FMfXP1O#8P*2oMY~frGI~6XZ7KyqB zR5C$iDhN%Lx=U|`TG!*`|IjG+asHbIbp#Ry$`(dbl&cU@1LK9E96kwUpmIR^%I;~= zV53zw(3-A`4$-Z?XP}fepwm?I1UcL3XfuTIMbp$$7S*~+;ex3N$*-eoY)2(8@1%OR zl+6VT#<)a=+r;ula*G^+)LWx{651X@wl}vLJuUFao}iE)CG*}ku9t=^ESq;Ov!^4e$@7Gwm-I&WI_4 zYE&`OfD<`07ZX3=8*9+bSDP@TP?aWf8>5*kqxK{y7PUHz!s3-5jW1Wdl31%$)s@*= zPOrFqFU+5Yb(cH6KE6C5zm!IA|N7$R3_ooC6D /dev/null && pwd ) + +if [[ "$(type -p diag2cbor.rb)" == "" ]]; then + echo "ERROR: please install ruby-cbor-diag package" + exit 1 +fi + +for case in "$THIS_DIR"/src/*.diag; do + outfile=$(basename "${case%%.diag}").cbor + + echo "generating $outfile" + + diag2cbor.rb "$case" > "$THIS_DIR/$outfile" +done + +echo "done." diff --git a/coev/testcases/src/ce-coswid.diag b/coev/testcases/src/ce-coswid.diag new file mode 100644 index 00000000..ad8f653e --- /dev/null +++ b/coev/testcases/src/ce-coswid.diag @@ -0,0 +1,39 @@ +/ tagged-concise-evidence / 571({ + / ce.ev-triples / 0 : { + / ce.coswid-triples / 4 : [ + [ /** ev-coswid-triple-record **/ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'0607517B010F046308'), / 2.1.123.1.15.4.99.8 / + / comid.vendor / 1 : "xyzinc.example" + } + }, + [ + / ev-coswid-evidence-map / { + / ce.coswid-tag-id / 0 : "com.acme.rrd2013-ce-sp1-v4-1-5-0", + / ce.coswid-evidence / 1 : { / *** evidence-entry in coswid ** / + / directory / 16: { + / fs-name / 24: "rrdetector", + / root / 25: "%programdata%", + / path-elements / 26: { + / file / 17: { + / fs-name / 24: "rrdetector.exe", + / size / 20: 532712, + / hash / 7: [ + / hash-alg-id / 1, + / hash-value / h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A' + ] + } + } + } + }, + / ce.authorized-by / 2 : [ + / tagged-pkix-base64-key-type / 554("base64_key_X") + ] + } + ] + ] + ] + } +}) \ No newline at end of file diff --git a/coev/testcases/src/ce-evidence.diag b/coev/testcases/src/ce-evidence.diag new file mode 100644 index 00000000..8d1334f0 --- /dev/null +++ b/coev/testcases/src/ce-evidence.diag @@ -0,0 +1,23 @@ + / tagged-concise-evidence / 571( { + / ce.ev-triples / 0 : { + / ce.evidence-triples / 0 : [ + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "xyzinc.example" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / digests / 2 : [ [ 1, h'A314FC2DC663AE7A6B6BC6787594057396E6B3F569CD50FD5DDB4D1BBAFD2B6A'] ] + }, + 2: 554("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==\n-----END PUBLIC KEY-----") + } + ] + ] + ] + } + } ) \ No newline at end of file diff --git a/coev/testcases/src/ce-identity.diag b/coev/testcases/src/ce-identity.diag new file mode 100644 index 00000000..3727d0cb --- /dev/null +++ b/coev/testcases/src/ce-identity.diag @@ -0,0 +1,27 @@ +/ tagged-concise-evidence / 571({ + / ce.ev-triples / 0 : { + / ce.identity-triples / 1 : [ + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner", + / layer / 3 : 1 + } + }, + [ + / tagged-pkix-base64-key-type / 554("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"), + / tagged-pkix-base64-cert-type / 555("-----BEGIN CERTIFICATE-----\nMIIB4TCCAYegAwIBAgIUGhrA9M3yQIFqckA2v6nQewmF30IwCgYIKoZIzj0EAwIw\nRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\ndGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzA5MDQxMTAxNDhaGA8yMDUxMDEx\nOTExMDE0OFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAf\nBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqG\nSM49AwEHA0IABFtQb6hfv68vAVmu2RDFNcWGBxEPAZS0+DBRzmjviAk7SL6xHhKa\n/7H0wD+uh9oQh3W+RxFyrUSqHekC3DizwNWjUzBRMB0GA1UdDgQWBBQWpNPb6eWD\nSM/+jwpbzoO3iHg4LTAfBgNVHSMEGDAWgBQWpNPb6eWDSM/+jwpbzoO3iHg4LTAP\nBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIAayNIF0eCJDZmcrqRjH\nf9h8GxeIDUnLqldeIvNfa+9SAiEA9ULBTPjnTUhYle226OAjg2sdhkXtb3Mu0E0F\nnuUmsIQ=\n-----END CERTIFICATE-----"), + / tagged-thumbprint-type / 557([ + / alg / 1, / sha256 / + / value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ]) + ] + ] + ] + } +}) \ No newline at end of file