Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 99 additions & 2 deletions conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
_ "crypto/sha256"
Expand All @@ -12,6 +13,7 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"io"
"math/big"
"os"
"path/filepath"
Expand Down Expand Up @@ -71,17 +73,20 @@ var testCases = []struct {
{name: "sign1-sign-0004", deterministic: true},
{name: "sign1-sign-0005", deterministic: true},
{name: "sign1-sign-0006", deterministic: true},
{name: "sign1-sign-0007", deterministic: true}, // EdDSA Ed25519
{name: "sign1-verify-0000"},
{name: "sign1-verify-0001"},
{name: "sign1-verify-0002"},
{name: "sign1-verify-0003"},
{name: "sign1-verify-0004"},
{name: "sign1-verify-0005"},
{name: "sign1-verify-0006"},
{name: "sign1-verify-0007"}, // EdDSA Ed25519
{name: "sign1-verify-negative-0000", err: "cbor: invalid protected header: cbor: require bstr type"},
{name: "sign1-verify-negative-0001", err: "cbor: invalid protected header: cbor: protected header: require map type"},
{name: "sign1-verify-negative-0002", err: "cbor: invalid protected header: cbor: found duplicate map key \"1\" at map element index 1"},
{name: "sign1-verify-negative-0003", err: "cbor: invalid unprotected header: cbor: found duplicate map key \"4\" at map element index 1"},
{name: "sign1-verify-negative-0004"}, // EdDSA Ed25519 invalid signature
}

func TestConformance(t *testing.T) {
Expand Down Expand Up @@ -153,6 +158,7 @@ func testSign1(t *testing.T, tc *TestCase, deterministic bool) {
if err != nil {
t.Fatal(err)
}
t.Logf("Test case: %s, Key type: %s", tc.Title, tc.Key["kty"])
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debug logging statements should be removed or replaced with conditional debug logging. These logs will clutter test output in normal runs and are typically only needed during development.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is great, changes will be made fr...

sig := tc.Sign1
sigMsg := cose.NewSign1Message()
sigMsg.Payload = mustHexToBytes(sig.Payload)
Expand All @@ -164,7 +170,16 @@ func testSign1(t *testing.T, tc *TestCase, deterministic bool) {
if sig.External != "" {
external = mustHexToBytes(sig.External)
}
err = sigMsg.Sign(new(zeroSource), external, signer)
// Ed25519 signatures are deterministic and should use nil for rand
// Other algorithms use zeroSource for reproducible test results
var rand io.Reader = new(zeroSource)
if tc.Alg == "EdDSA" {
rand = nil
}
t.Logf("Algorithm: %s, using rand: %v", tc.Alg, rand)
t.Logf("Payload: %x", sigMsg.Payload)
t.Logf("Protected: %+v", sigMsg.Headers.Protected)
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debug logging statements should be removed or replaced with conditional debug logging. These logs will clutter test output in normal runs and are typically only needed during development.

Suggested change
t.Logf("Protected: %+v", sigMsg.Headers.Protected)
if testing.Verbose() {
t.Logf("Protected: %+v", sigMsg.Headers.Protected)
}

Copilot uses AI. Check for mistakes.
err = sigMsg.Sign(rand, external, signer)
if err != nil {
t.Fatal(err)
}
Expand All @@ -187,11 +202,57 @@ func testSign1(t *testing.T, tc *TestCase, deterministic bool) {
}

func getSigner(tc *TestCase, private bool) (cose.Signer, cose.Verifier, error) {
alg := mustNameToAlg(tc.Alg)

// Special handling for OKP keys
if tc.Key["kty"] == "OKP" {
switch tc.Key["crv"] {
case "Ed25519":
publicKey := mustBase64ToBytes(tc.Key["x"])
if len(publicKey) != ed25519.PublicKeySize {
return nil, nil, errors.New("invalid Ed25519 public key size")
}
// Note: Ed448 would require different key size validation (57 bytes)

var signer cose.Signer
var verifierKey ed25519.PublicKey

if private {
privateKey := mustBase64ToBytes(tc.Key["d"])
if len(privateKey) != ed25519.SeedSize {
return nil, nil, errors.New("invalid Ed25519 private key size")
}
fullPrivateKey := ed25519.NewKeyFromSeed(privateKey)
var err error
signer, err = cose.NewSigner(alg, fullPrivateKey)
if err != nil {
return nil, nil, err
}
// Use the public key from the private key for verification
verifierKey = fullPrivateKey.Public().(ed25519.PublicKey)
} else {
// For verify-only, use the public key from the test case
verifierKey = ed25519.PublicKey(publicKey)
}

verifier, err := cose.NewVerifier(alg, verifierKey)
if err != nil {
return nil, nil, err
}
return signer, verifier, nil
case "Ed448":
// Ed448 support would go here - requires golang.org/x/crypto/ed448
return nil, nil, errors.New("Ed448 not yet implemented")
default:
return nil, nil, errors.New("unsupported OKP curve: " + tc.Key["crv"])
}
}

// Original implementation for RSA and EC keys
pkey, err := getKey(tc.Key, private)
if err != nil {
return nil, nil, err
}
alg := mustNameToAlg(tc.Alg)
signer, err := cose.NewSigner(alg, pkey)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -248,6 +309,32 @@ func getKey(key Key, private bool) (crypto.Signer, error) {
pkey.D = mustBase64ToBigInt(key["d"])
}
return pkey, nil
case "OKP":
// Only Ed25519 is supported for now
switch key["crv"] {
case "Ed25519":
publicKey := mustBase64ToBytes(key["x"])
if len(publicKey) != ed25519.PublicKeySize {
return nil, errors.New("invalid Ed25519 public key size")
}
if private {
privateKey := mustBase64ToBytes(key["d"])
if len(privateKey) != ed25519.SeedSize {
return nil, errors.New("invalid Ed25519 private key size")
}
// Ed25519 private key is 64 bytes: 32-byte seed + 32-byte public key
fullPrivateKey := ed25519.NewKeyFromSeed(privateKey)
return fullPrivateKey, nil
}
// For public key only operations, we need to return a type that satisfies crypto.Signer
// but this won't work for verify-only operations. We'll handle this in getSigner.
return nil, errors.New("OKP public-only key not supported in this context")
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OKP key handling logic is duplicated between the getKey function and the getSigner function. Consider extracting the Ed25519 key creation logic into a separate helper function to reduce code duplication.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very valid comment, which needs to be incorporated!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok sir @yogeshbdeshpande I will more review this one...

case "Ed448":
// Ed448 support would require golang.org/x/crypto/ed448
return nil, errors.New("Ed448 not yet implemented")
default:
return nil, errors.New("unsupported OKP curve: " + key["crv"])
}
}
return nil, errors.New("unsupported key type: " + key["kty"])
}
Expand Down Expand Up @@ -297,6 +384,14 @@ func mustBase64ToBigInt(s string) *big.Int {
return new(big.Int).SetBytes(val)
}

func mustBase64ToBytes(s string) []byte {
val, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
panic(err)
}
return 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.
Expand All @@ -320,6 +415,8 @@ func mustNameToAlg(name string) cose.Algorithm {
return cose.AlgorithmES384
case "ES512":
return cose.AlgorithmES512
case "EdDSA":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this could also mean Ed448.

Consider if anything here is helpful:

https://datatracker.ietf.org/doc/draft-ietf-jose-fully-specified-algorithms/13/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure sir @OR13 , thanks for the approval

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi sir @OR13 Good point about Ed448. I've added placeholder support for it with proper error messages and comments about the key size differences (57-byte vs 32-byte seeds). The code structure now makes it easy to add Ed448 implementation later when golang.org/x/crypto/ed448 is integrated. Both curves correctly use AlgorithmEdDSA = -8 with curve differentiation in the key's "crv" field.

return cose.AlgorithmEdDSA
}
panic("algorithm name not found: " + name)
}
34 changes: 34 additions & 0 deletions testdata/sign1-sign-0007.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"uuid": "ED25519-SIGN-0001",
"title": "Sign1 - EdDSA with Ed25519 (sign)",
"description": "Sign with one signer using EdDSA with Ed25519",
"key": {
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"
},
"alg": "EdDSA",
"sign1::sign": {
"payload": "546869732069732074686520636f6e74656e742e",
"protectedHeaders": {
"cborHex": "a201270300",
"cborDiag": "{1: -8, 3: 0}"
},
"unprotectedHeaders": {
"cborHex": "a104423131",
"cborDiag": "{4: '11'}"
},
"tbsHex": {
"cborHex": "846a5369676e61747572653145a20127030040545468697320697320746865206f6e74656e742e",
"cborDiag": "[\"Signature1\", h'A201270300', h'', h'546869732069732074686520636F6E74656E742E']"
},
"external": "",
"detached": false,
"expectedOutput": {
"cborHex": "d28445a201270300a10442313154546869732069732074686520636f6e74656e742e58407142fd2ff96d56db85bee905a76ba1d0b7321a95c8c4d3607c5781932b7afb8711497dfa751bf40b58b3bcc32300b1487f3db34085eef013bf08f4a44d6fef0d",
"cborDiag": "18([h'A201270300', {4: h'3131'}, h'546869732069732074686520636F6E74656E742E', h'7142FD2FF96D56DB85BEE905A76BA1D0B7321A95C8C4D3607C5781932B7AFB8711497DFA751BF40B58B3BCC32300B1487F3DB34085EEF013BF08F4A44D6FEF0D'])"
},
"fixedOutputLength": 32
}
}
19 changes: 19 additions & 0 deletions testdata/sign1-verify-0007.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"uuid": "ED25519-VERIFY-0001",
"title": "Sign1 - EdDSA with Ed25519 (verify)",
"description": "Verify signature with one signer using EdDSA with Ed25519",
"key": {
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
},
"alg": "EdDSA",
"sign1::verify": {
"taggedCOSESign1": {
"cborHex": "d28445a201270300a10442313154546869732069732074686520636f6e74656e742e58407142fd2ff96d56db85bee905a76ba1d0b7321a95c8c4d3607c5781932b7afb8711497dfa751bf40b58b3bcc32300b1487f3db34085eef013bf08f4a44d6fef0d",
"cborDiag": "18([h'A201270300', {4: h'3131'}, h'546869732069732074686520636F6E74656E742E', h'7142FD2FF96D56DB85BEE905A76BA1D0B7321A95C8C4D3607C5781932B7AFB8711497DFA751BF40B58B3BCC32300B1487F3DB34085EEF013BF08F4A44D6FEF0D'])"
},
"external": "",
"shouldVerify": true
}
}
19 changes: 19 additions & 0 deletions testdata/sign1-verify-negative-0004.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"uuid": "ED25519-VERIFY-NEGATIVE-0001",
"title": "Sign1 - EdDSA with Ed25519 (verify - invalid signature)",
"description": "Verification should fail with invalid Ed25519 signature",
"key": {
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
},
"alg": "EdDSA",
"sign1::verify": {
"taggedCOSESign1": {
"cborHex": "d28445a201270300a104423131545468697320697320746865206f6e74656e742e5840ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"cborDiag": "18([h'A201270300', {4: h'3131'}, h'546869732069732074686520636F6E74656E742E', h'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'])"
},
"external": "",
"shouldVerify": false
}
}