Skip to content

Commit

Permalink
add conformance test (#57)
Browse files Browse the repository at this point in the history
Signed-off-by: Junjie Gao <[email protected]>

Signed-off-by: Junjie Gao <[email protected]>
Co-authored-by: Junjie Gao <[email protected]>
  • Loading branch information
JeyJeyGao and JeyJeyGao authored Aug 29, 2022
1 parent 89bf762 commit 80a50e6
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 0 deletions.
180 changes: 180 additions & 0 deletions signature/jws/conformance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package jws

import (
"crypto/elliptic"
"crypto/x509"
"encoding/json"
"os"
"reflect"
"sort"
"strings"
"testing"
"time"

"github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-core-go/testhelper"
)

var (
// prepare signing time
signingTime, _ = time.Parse("2006-01-02 15:04:05", "2022-08-29 13:50:00")
expiry, _ = time.Parse("2006-01-02 15:04:05", "2099-08-29 13:50:00")
// signedAttributes for signing request
signedAttributes = signature.SignedAttributes{
SigningScheme: "notary.x509",
SigningTime: signingTime.Truncate(time.Second),
Expiry: expiry.Truncate(time.Second).Add(time.Hour * 24),
ExtendedAttributes: sortAttributes([]signature.Attribute{
{Key: "signedCritKey1", Value: "signedCritValue1", Critical: true},
{Key: "signedKey1", Value: "signedValue1", Critical: false},
{Key: "signedKey2", Value: "signedValue1", Critical: false},
{Key: "signedKey3", Value: "signedValue1", Critical: false},
{Key: "signedKey4", Value: "signedValue1", Critical: false},
}),
}
// unsignedAttributes for signing request
unsignedAttributes = signature.UnsignedAttributes{
SigningAgent: "NotationConformanceTest/1.0.0",
}
// payload to be signed
payload = signature.Payload{
ContentType: "application/vnd.cncf.notary.payload.v1+json",
Content: []byte("hello JWS"),
}
// certificate chain for signer
leafCertTuple = testhelper.GetECCertTuple(elliptic.P256())
certs = []*x509.Certificate{leafCertTuple.Cert, testhelper.GetECRootCertificate().Cert}
)

func conformanceTestSignReq() *signature.SignRequest {
signer, err := signature.NewLocalSigner(certs, leafCertTuple.PrivateKey)
if err != nil {
panic(err)
}

return &signature.SignRequest{
Payload: payload,
Signer: signer,
SigningTime: signedAttributes.SigningTime,
Expiry: signedAttributes.Expiry,
ExtendedSignedAttributes: signedAttributes.ExtendedAttributes,
SigningAgent: unsignedAttributes.SigningAgent,
SigningScheme: signedAttributes.SigningScheme,
}
}

// TestSignedMessageConformance check the conformance between the encoded message
// and the valid encoded message in conformance.json
//
// check payload, protected and signingAgent section
func TestSignedMessageConformance(t *testing.T) {
// get encoded message
env := envelope{}
signReq := conformanceTestSignReq()
encoded, err := env.Sign(signReq)
checkNoError(t, err)

// parse encoded message to be a map
envMap, err := unmarshalEncodedMessage(encoded)
checkNoError(t, err)
// load validation encoded message
validEnvMap, err := getValidEnvelopeMap()
checkNoError(t, err)

// check payload section conformance
if !reflect.DeepEqual(envMap["payload"], validEnvMap["payload"]) {
t.Fatal("signed message payload test failed.")
}

// check protected section conformance
if !reflect.DeepEqual(envMap["protected"], validEnvMap["protected"]) {
t.Fatal("signed message protected test failed.")
}

// prepare header
header, ok := envMap["header"].(map[string]interface{})
if !ok {
t.Fatal("signed message header format error.")
}
validHeader, ok := validEnvMap["header"].(map[string]interface{})
if !ok {
t.Fatal("conformance.json header format error.")
}
// check io.cncf.notary.signingAgent conformance
if !reflect.DeepEqual(header["io.cncf.notary.signingAgent"], validHeader["io.cncf.notary.signingAgent"]) {
t.Fatal("signed message signingAgent test failed.")
}
}

func getValidEnvelopeMap() (map[string]interface{}, error) {
encoded, err := os.ReadFile("./testdata/conformance.json")
if err != nil {
return nil, err
}
return unmarshalEncodedMessage(encoded)
}

func unmarshalEncodedMessage(encoded []byte) (envelopeMap map[string]interface{}, err error) {
err = json.Unmarshal(encoded, &envelopeMap)
return
}

// TestVerifyConformance generates JWS encoded message, parses the encoded message and
// verify the payload, signed/unsigned attributes conformance.
func TestVerifyConformance(t *testing.T) {
env := envelope{}
signReq := conformanceTestSignReq()
encoded, err := env.Sign(signReq)
checkNoError(t, err)

newEnv, err := ParseEnvelope(encoded)
checkNoError(t, err)

// verify validity
payload, signerInfo, err := newEnv.Verify()
checkNoError(t, err)

// check payload conformance
verifyPayload(t, payload)

// check signed/unsigned attributes conformance
verifyAttributes(t, signerInfo)
}

func verifyPayload(t *testing.T, gotPayload *signature.Payload) {
if !reflect.DeepEqual(&payload, gotPayload) {
t.Fatalf("verify payload failed. want: %+v got: %+v\n", &payload, gotPayload)
}
}

func verifyAttributes(t *testing.T, signerInfo *signature.SignerInfo) {
// check unsigned attributes
if !reflect.DeepEqual(&unsignedAttributes, &signerInfo.UnsignedAttributes) {
t.Fatalf("verify UnsignedAttributes failed. want: %+v got: %+v\n", &unsignedAttributes, &signerInfo.UnsignedAttributes)
}

// check signed attributes
sortAttributes(signerInfo.SignedAttributes.ExtendedAttributes)
if !reflect.DeepEqual(&signedAttributes, &signerInfo.SignedAttributes) {
t.Fatalf("verify SignedAttributes failed. want: %+v got: %+v\n", &signedAttributes, &signerInfo.SignedAttributes)
}

// check signature algorithm
keySpec, err := signature.ExtractKeySpec(certs[0])
checkNoError(t, err)
if keySpec.SignatureAlgorithm() != signerInfo.SignatureAlgorithm {
t.Fatalf("verify signature algorithm failed. want: %d got: %d\n", keySpec.SignatureAlgorithm(), signerInfo.SignatureAlgorithm)
}

// check certificate chain
if !reflect.DeepEqual(signerInfo.CertificateChain, certs) {
t.Fatalf("verify certificate chain failed. want: %+v got: %+v\n", &signerInfo.CertificateChain, certs)
}
}

func sortAttributes(attributes []signature.Attribute) []signature.Attribute {
sort.Slice(attributes, func(i, j int) bool {
return strings.Compare(attributes[i].Key, attributes[j].Key) < 0
})
return attributes
}
8 changes: 8 additions & 0 deletions signature/jws/testdata/conformance.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"payload": "ImFHVnNiRzhnU2xkVCI",
"protected": "eyJhbGciOiJFUzI1NiIsImNyaXQiOlsiaW8uY25jZi5ub3Rhcnkuc2lnbmluZ1NjaGVtZSIsInNpZ25lZENyaXRLZXkxIiwiaW8uY25jZi5ub3RhcnkuZXhwaXJ5Il0sImN0eSI6ImFwcGxpY2F0aW9uL3ZuZC5jbmNmLm5vdGFyeS5wYXlsb2FkLnYxK2pzb24iLCJpby5jbmNmLm5vdGFyeS5leHBpcnkiOiIyMDk5LTA4LTMwVDEzOjUwOjAwWiIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdTY2hlbWUiOiJub3RhcnkueDUwOSIsImlvLmNuY2Yubm90YXJ5LnNpZ25pbmdUaW1lIjoiMjAyMi0wOC0yOVQxMzo1MDowMFoiLCJzaWduZWRDcml0S2V5MSI6InNpZ25lZENyaXRWYWx1ZTEiLCJzaWduZWRLZXkxIjoic2lnbmVkVmFsdWUxIiwic2lnbmVkS2V5MiI6InNpZ25lZFZhbHVlMSIsInNpZ25lZEtleTMiOiJzaWduZWRWYWx1ZTEiLCJzaWduZWRLZXk0Ijoic2lnbmVkVmFsdWUxIn0",
"header": {
"io.cncf.notary.signingAgent": "NotationConformanceTest/1.0.0"
},
"signature": "2iu9nIOou8NjVUvOSQ8h0n-ZMyrh33quqXQNbmc1C7G7RmWngttZmh8TxcmuQshKtwN1LW8Jf3iqTHlhM6ggcQ"
}

0 comments on commit 80a50e6

Please sign in to comment.