Skip to content

Commit

Permalink
add conformance tests
Browse files Browse the repository at this point in the history
Signed-off-by: qmuntal <qmuntaldiaz@microsoft.com>
  • Loading branch information
qmuntal committed Mar 29, 2022
1 parent dee3b3e commit bb84bb8
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 0 deletions.
283 changes: 283 additions & 0 deletions conformance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
package cose_test

import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"math/big"
"os"
"path/filepath"
"testing"

"github.com/veraison/go-cose"
)

type TestCase struct {
UUID string `json:"uuid"`
Title string `json:"title"`
Description string `json:"description"`
Key Key `json:"key"`
Alg string `json:"alg"`
Sign1 *Sign1 `json:"sign1::sign"`
Verify1 *Verify1 `json:"sign1::verify"`
}

type Key map[string]string

type Sign1 struct {
Payload string `json:"payload"`
ProtectedHeaders *CBOR `json:"protectedHeaders"`
UnprotectedHeaders *CBOR `json:"unprotectedHeaders"`
External string `json:"external"`
Detached bool `json:"detached"`
TBS CBOR `json:"tbsHex"`
Output CBOR `json:"expectedOutput"`
OutputLength int `json:"fixedOutputLength"`
}

type Verify1 struct {
TaggedCOSESign1 CBOR `json:"taggedCOSESign1"`
External string `json:"external"`
Verify bool `json:"shouldVerify"`
}

type CBOR struct {
CBORHex string `json:"cborHex"`
CBORDiag string `json:"cborDiag"`
}

// Conformance samples are taken from
// https://github.com/cose-wg/Examples.
var testCases = []string{
"sign1-sign-0000",
"sign1-sign-0001",
"sign1-sign-0002",
"sign1-sign-0003",
"sign1-verify-0000",
"sign1-verify-0001",
"sign1-verify-0002",
"sign1-verify-0003",
}

func TestConformance(t *testing.T) {
for _, name := range testCases {
t.Run(name, func(t *testing.T) {
data, err := os.ReadFile(filepath.Join("testdata", name+".json"))
if err != nil {
t.Fatal(err)
}
var tc TestCase
err = json.Unmarshal(data, &tc)
if err != nil {
t.Fatal(err)
}
processTestCase(t, &tc)
})
}
}

func processTestCase(t *testing.T, tc *TestCase) {
if tc.Sign1 != nil {
testSign1(t, tc)
} else if tc.Verify1 != nil {
testVerify1(t, tc)
} else {
t.Fatal("test case not supported")
}
}

func testVerify1(t *testing.T, tc *TestCase) {
signer, err := getSigner(tc, false)
if err != nil {
t.Fatal(err)
}
var sigMsg cose.Sign1Message
err = sigMsg.UnmarshalCBOR(mustHexToBytes(tc.Verify1.TaggedCOSESign1.CBORHex))
if err != nil {
t.Fatal(err)
}
external := []byte("")
if tc.Verify1.External != "" {
external = mustHexToBytes(tc.Verify1.External)
}
err = sigMsg.Verify(external, *signer.Verifier())
if tc.Verify1.Verify && err != nil {
t.Fatal(err)
} else if !tc.Verify1.Verify && err == nil {
t.Fatal("Verify1 should have failed")
}
}

func testSign1(t *testing.T, tc *TestCase) {
signer, err := getSigner(tc, true)
if err != nil {
t.Fatal(err)
}
sig := tc.Sign1
sigMsg := cose.NewSign1Message()
sigMsg.Payload = mustHexToBytes(sig.Payload)
sigMsg.Headers, err = decodeHeaders(mustHexToBytes(sig.ProtectedHeaders.CBORHex), mustHexToBytes(sig.UnprotectedHeaders.CBORHex))
if err != nil {
t.Fatal(err)
}
external := []byte("")
if sig.External != "" {
external = mustHexToBytes(sig.External)
}
err = sigMsg.Sign(new(zeroSource), external, *signer)
if err != nil {
t.Fatal(err)
}
err = sigMsg.Verify(external, *signer.Verifier())
if err != nil {
t.Fatal(err)
}
got, err := sigMsg.MarshalCBOR()
if err != nil {
t.Fatal(err)
}
want := mustHexToBytes(sig.Output.CBORHex)
if sig.OutputLength > 0 {
got = got[:sig.OutputLength]
want = want[:sig.OutputLength]
}
if !bytes.Equal(want, got) {
t.Fatalf("unexpected output:\nwant: %x\n got: %x", want, got)
}
}

func getSigner(tc *TestCase, private bool) (*cose.Signer, error) {
pkey, err := getKey(tc.Key, private)
if err != nil {
return nil, err
}
alg := mustNameToAlg(tc.Alg)
signer, err := cose.NewSignerFromKey(alg, pkey)
if err != nil {
return nil, err
}
return signer, nil
}

func getKey(key Key, private bool) (crypto.PrivateKey, error) {
switch key["kty"] {
case "EC":
var c elliptic.Curve
switch key["crv"] {
case "P-224":
c = elliptic.P224()
case "P-256":
c = elliptic.P256()
case "P-384":
c = elliptic.P384()
case "P-521":
c = elliptic.P521()
default:
return nil, errors.New("unsupported EC curve: " + key["crv"])
}
pkey := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
X: mustBase64ToBigInt(key["x"]),
Y: mustBase64ToBigInt(key["y"]),
Curve: c,
},
}
if private {
pkey.D = mustBase64ToBigInt(key["d"])
}
return pkey, nil
}
return nil, errors.New("unsupported key type: " + key["kty"])
}

// zeroSource is an io.Reader that returns an unlimited number of zero bytes.
type zeroSource struct{}

func (zeroSource) Read(b []byte) (n int, err error) {
for i := range b {
b[i] = 0
}

return len(b), nil
}

func decodeHeaders(protected, unprotected []byte) (*cose.Headers, error) {
var hdr cose.Headers
hdr.Protected = make(map[interface{}]interface{})
hdr.Unprotected = make(map[interface{}]interface{})
err := hdr.DecodeProtected(protected)
if err != nil {
return nil, err
}
b, err := cose.Unmarshal(unprotected)
if err != nil {
return nil, err
}
err = hdr.DecodeUnprotected(b)
if err != nil {
return nil, err
}
hdr.Protected = fixHeader(hdr.Protected)
hdr.Unprotected = fixHeader(hdr.Unprotected)
return &hdr, nil
}

func fixHeader(m map[interface{}]interface{}) map[interface{}]interface{} {
ret := make(map[interface{}]interface{})
for k, v := range m {
switch k1 := k.(type) {
case int64:
k = int(k1)
}
switch v1 := v.(type) {
case int64:
v = int(v1)
}
ret[k] = v
}
return ret
}

func mustHexToInt(s string) int {
return int(mustHexToBigInt(s).Int64())
}

func mustHexToBytes(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}

func mustHexToBigInt(s string) *big.Int {
return new(big.Int).SetBytes(mustHexToBytes(s))
}

func mustBase64ToBigInt(s string) *big.Int {
val, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
panic(err)
}
return new(big.Int).SetBytes(val)
}

// mustNameToAlg returns the algorithm associated to name.
// The content of name is not defined in any RFC,
// but it's what the test cases use to identify algorithms.
func mustNameToAlg(name string) *cose.Algorithm {
switch name {
case "ES256":
return cose.ES256
case "ES384":
return cose.ES384
case "ES512":
return cose.ES512
}
panic("algorithm name not found: " + name)
}
35 changes: 35 additions & 0 deletions testdata/sign1-sign-0000.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"uuid": "D55A49BD-53D9-42B1-9E76-E0CF2AD33E9D",
"title": "Sign1 w/ external input - ECDSA w/ SHA-256 (sign)",
"description": "Sign with one signer using ECDSA w/ SHA-256 supplying external input",
"key": {
"kty": "EC",
"crv": "P-256",
"x": "usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8",
"y": "IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4",
"d": "V8kgd2ZBRuh2dgyVINBUqpPDr7BOMGcF22CQMIUHtNM"
},
"alg": "ES256",
"sign1::sign": {
"payload": "546869732069732074686520636f6e74656e742e",
"protectedHeaders": {
"cborHex": "a10126",
"cborDiag": "{1: -7}"
},
"unprotectedHeaders": {
"cborHex": "a104423131",
"cborDiag": "{4: '11'}"
},
"tbsHex": {
"cborHex": "846a5369676e61747572653143a101264c11aa22bb33cc44dd5500669954546869732069732074686520636f6e74656e742e",
"cborDiag": "[\"Signature1\", h'A10126', h'11AA22BB33CC44DD55006699', h'546869732069732074686520636F6E74656E742E']"
},
"external": "11aa22bb33cc44dd55006699",
"detached": false,
"expectedOutput": {
"cborHex": "d28443a10126a10442313154546869732069732074686520636f6e74656e742e58403a7487d9a528cb61dd8e99bd652c12577fc47d70ee5af2e703c420584f060fc7a8d61e4a35862b2b531a8447030ab966aeed8dd45ebc507c761431e349995770",
"cborDiag": "18([h'A10126', {4: '11'}, h'546869732069732074686520636F6E74656E742E', h'3A7487D9A528CB61DD8E99BD652C12577FC47D70EE5AF2E703C420584F060FC7A8D61E4A35862B2B531A8447030AB966AEED8DD45EBC507C761431E349995770'])"
},
"fixedOutputLength": 32
}
}
34 changes: 34 additions & 0 deletions testdata/sign1-sign-0001.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"uuid": "0F78DB1C-C30F-47B1-AF19-6D0C0B2F3803",
"title": "Sign1 - ECDSA w/ SHA-256 (sign)",
"description": "Sign with one signer using ECDSA w/ SHA-256",
"key": {
"kty": "EC",
"crv": "P-256",
"x": "usWxHK2PmfnHKwXPS54m0kTcGJ90UiglWiGahtagnv8",
"y": "IBOL-C3BttVivg-lSreASjpkttcsz-1rb7btKLv8EX4",
"d": "V8kgd2ZBRuh2dgyVINBUqpPDr7BOMGcF22CQMIUHtNM"
},
"alg": "ES256",
"sign1::sign": {
"payload": "546869732069732074686520636f6e74656e742e",
"protectedHeaders": {
"cborHex": "a201260300",
"cborDiag": "{1: -7, 3: 0}"
},
"unprotectedHeaders": {
"cborHex": "a104423131",
"cborDiag": "{4: '11'}"
},
"tbsHex": {
"cborHex": "846a5369676e61747572653145a2012603004054546869732069732074686520636f6e74656e742e",
"cborDiag": "[\"Signature1\", h'A201260300', h'', h'546869732069732074686520636F6E74656E742E']"
},
"detached": false,
"expectedOutput": {
"cborHex": "d28445a201260300a10442313154546869732069732074686520636f6e74656e742e58402ad3b9dcc1e13d04f357e11cc8acd825196620e62f0d8deca72672508b829d90e07a3f23be6aa36fd6ebd31e2ed08d1760bffd981f991bfc94a45199a54875c4",
"cborDiag": "18([h'A201260300', {4: '11'}, h'546869732069732074686520636F6E74656E742E', h'2AD3B9DCC1E13D04F357E11CC8ACD825196620E62F0D8DECA72672508B829D90E07A3F23BE6AA36FD6EBD31E2ED08D1760BFFD981F991BFC94A45199A54875C4'])"
},
"fixedOutputLength": 34
}
}
35 changes: 35 additions & 0 deletions testdata/sign1-sign-0002.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"uuid": "E693D0C8-C702-4E6C-A70D-0D4DA4C408A0",
"title": "Sign1 - ECDSA w/ SHA-384 (sign)",
"description": "Sign with one signer using ECDSA w/ SHA-384",
"key": {
"kty": "EC",
"kid": "P384",
"crv": "P-384",
"x": "kTJyP2KSsBBhnb4kjWmMF7WHVsY55xUPgb7k64rDcjatChoZ1nvjKmYmPh5STRKc",
"y": "mM0weMVU2DKsYDxDJkEP9hZiRZtB8fPfXbzINZj_fF7YQRynNWedHEyzAJOX2e8s",
"d": "ok3Nq97AXlpEusO7jIy1FZATlBP9PNReMU7DWbkLQ5dU90snHuuHVDjEPmtV0fTo"
},
"alg": "ES384",
"sign1::sign": {
"payload": "546869732069732074686520636f6e74656e742e",
"protectedHeaders": {
"cborHex": "a1013822",
"cborDiag": "{1: -35}"
},
"unprotectedHeaders": {
"cborHex": "a1044450333834",
"cborDiag": "{4: 'P384'}"
},
"tbsHex": {
"cborHex": "846a5369676e61747572653144a10138224054546869732069732074686520636f6e74656e742e",
"cborDiag": "[\"Signature1\", h'A1013822', h'', h'546869732069732074686520636F6E74656E742E']"
},
"detached": false,
"expectedOutput": {
"cborHex": "d28444a1013822a104445033383454546869732069732074686520636f6e74656e742e5860aa46c1ab71cd3c1e68ed62c27653797cb72cba3a856fd5e2f38794eee0d666e88139ec51fb62466f4865ca56df493905911e329e829c1887f6259681360a8e7f7d3fd080dcb0720066f13e1621656700c99d6e3771ac2549fde998ee9b1e2cad",
"cborDiag": "18([h'A1013822', {4: 'P384'}, h'546869732069732074686520636F6E74656E742E', h'AA46C1AB71CD3C1E68ED62C27653797CB72CBA3A856FD5E2F38794EEE0D666E88139EC51FB62466F4865CA56DF493905911E329E829C1887F6259681360A8E7F7D3FD080DCB0720066F13E1621656700C99D6E3771AC2549FDE998EE9B1E2CAD'])"
},
"fixedOutputLength": 35
}
}
Loading

0 comments on commit bb84bb8

Please sign in to comment.