Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update: enabled cose to verify time in Tag1 Datetime format #95

Merged
merged 10 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
6 changes: 2 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ module github.com/notaryproject/notation-core-go
go 1.18

require (
github.com/fxamacker/cbor/v2 v2.4.0
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/veraison/go-cose v1.0.0-rc.2
)

require (
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
)
require github.com/x448/float16 v0.8.4 // indirect
8 changes: 3 additions & 5 deletions signature/cose/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
)

type sign1 struct {
SigningTime int64 `json:"signingTime"`
Expiry int64 `json:"expiry"`
Payload string `json:"payload"`
ProtectedHeaders *cborStruct `json:"protectedHeaders"`
UnprotectedHeaders *cborStruct `json:"unprotectedHeaders"`
Expand Down Expand Up @@ -125,8 +123,8 @@ func getSignReq(sign1 *sign1) (*signature.SignRequest, error) {
Content: []byte("hello COSE"),
},
Signer: signer,
SigningTime: time.Unix(sign1.SigningTime, 0),
Expiry: time.Unix(sign1.Expiry, 0),
SigningTime: time.Unix(1661321924, 0),
Expiry: time.Unix(1661408324, 0),
ExtendedSignedAttributes: []signature.Attribute{
{Key: "signedCritKey1", Value: "signedCritValue1", Critical: true},
{Key: "signedKey1", Value: "signedValue1", Critical: false},
Expand Down Expand Up @@ -195,7 +193,7 @@ func generateSign1(msg *cose.Sign1Message) *cose.Sign1Message {
newMsg.Headers.Protected = msg.Headers.Protected
newMsg.Headers.Unprotected["io.cncf.notary.signingAgent"] = msg.Headers.Unprotected["io.cncf.notary.signingAgent"]
newMsg.Payload = msg.Payload
newMsg.Signature = hexToBytes("31b6cb0cd9c974b39d603465811c2aa3d96a5dff89f80b33cb4e321dc6e68a29b4ba65c00f0f9f22ee4376abfaec2cba6fd21c6881ecaab25775e3fb9226a88cf41660b2d6fd14184540d07ded3744e19ff9dbdd081e15c8f77bb6ca3072ef57141594fad4ea57d206c6b8dd3a6e0a0a7ed764ff08dbcc439bd722e1b3d282921a579a3d860cceea37d633184f9316cb6b4fa4ea550da5ad9e5bf3c2d768a787da76e594290cb10b5b1ead8b7e75967de28e9ff429fe9db814380608a15674f9741563902a620f312213d9dce5c264017cbcb3bb4f8cebee0d5ef32b364f68c11cba5630fac8e3165d06fdebaca095267223c552fe605b4529f25b65f8fa47b010b9096cec275307e82b1062f660a73e07d0b85b978b4a59b5cde51fc9a031b488a3deb38fc312a64ef2ec1250238ae16cfefc00d9aa1ceb938fe6de51f265eebe975c29f4cff8ab0afb40c45e8c985d17347bf20f455851c1a46ab655f51a159cf8910a424c5a8bbdd239e49e43a73c7b5174de29e835063e5e64b459558de5")
newMsg.Signature = hexToBytes("5bfec0a345098b9b9b6fb7358face7ab76d191b648ccd19e36fb03c2085ea072ec050d9c6e4fa4845478386d0831a2360d343a1ff027bdd56d496f996b90ac2db9da2460baffec21db7c0ca759ba83ab35cdf521c0926138681bde05277e2976cedbeb4040c930908ef2b113d935378bd3c5e7740119b2b81c59e9c6c24411abdf699547864f68f2e0f6346eeff627bf0d971abdf94e67e12a10134ccbbadfa0ab4031b18705696a9567a0f1f061247fdd00d343ea3a45f63da7f80771612b38fc9877375bcbce28aef1f3ee2b25869722c24737c49d8c6711376dd62b3d32b24d489746e2ba5d25fa76febcc6abf9d2baee67221c85a7a8f8763dadc5e20bb8c5c03a75c68211557813d2d6adea56ec5526f78c18460b1944c8307a4b0ed64a6d6b4abed5067de5a5ad38948a2ea140b01a7762c15b3e63d7d7bdc8962e6c4bff18b34d2a19fc627f02ebf88daf7fb25c55ce1b9ca06ade02f9d60ad16cb306f433f692e598132d67b5d0a02193191d5c9cd52ad81f4e31917e5b5d40ef5ce7")
return newMsg
}

Expand Down
90 changes: 77 additions & 13 deletions signature/cose/envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"crypto"
"crypto/rand"
"crypto/x509"
"errors"
"fmt"
"io"
"strconv"
"time"

"github.com/fxamacker/cbor/v2"
"github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-core-go/signature/internal/base"
"github.com/veraison/go-cose"
Expand Down Expand Up @@ -55,6 +57,17 @@ var signingSchemeTimeLabelMap = map[signature.SigningScheme]string{
signature.SigningSchemeX509SigningAuthority: headerLabelAuthenticSigningTime,
}

// Encoding options used in Sign
patrickzheng200 marked this conversation as resolved.
Show resolved Hide resolved
var encOpts = cbor.EncOptions{
Time: cbor.TimeUnix,
TimeTag: cbor.EncTagRequired,
}

// Decoding options used in Content
patrickzheng200 marked this conversation as resolved.
Show resolved Hide resolved
var decOpts = cbor.DecOptions{
TimeTag: cbor.DecTagRequired,
}

// signer interface is a cose.Signer with certificate chain fetcher.
type signer interface {
cose.Signer
Expand Down Expand Up @@ -142,7 +155,8 @@ func (signer *localSigner) CertificateChain() []*x509.Certificate {
}

type envelope struct {
base *cose.Sign1Message
isSign bool
base *cose.Sign1Message
}

// NewEnvelope initializes an empty COSE signature envelope.
Expand Down Expand Up @@ -204,6 +218,8 @@ func (e *envelope) Sign(req *signature.SignRequest) ([]byte, error) {
return nil, &signature.InvalidSignatureError{Msg: err.Error()}
}
e.base = msg
e.isSign = true

return encoded, nil
}

Expand Down Expand Up @@ -296,7 +312,7 @@ func (e *envelope) signerInfo() (*signature.SignerInfo, error) {

// parse protected headers of COSE envelope and populate related
// signerInfo fields
err := parseProtectedHeaders(e.base.Headers.Protected, &signerInfo)
err := parseProtectedHeaders(e.base.Headers.Protected, &signerInfo, e.isSign)
if err != nil {
return nil, &signature.InvalidSignatureError{Msg: err.Error()}
}
Expand Down Expand Up @@ -388,20 +404,34 @@ func generateProtectedHeaders(req *signature.SignRequest, protected cose.Protect
crit := []interface{}{headerLabelSigningScheme}
protected[headerLabelSigningScheme] = string(req.SigningScheme)

// generate Tag1 Datetime CBOR object
encMode, err := encOpts.EncMode()
if err != nil {
return &signature.InvalidSignRequestError{Msg: err.Error()}
}
patrickzheng200 marked this conversation as resolved.
Show resolved Hide resolved

// signingTime/authenticSigningTime
signingTimeLabel, ok := signingSchemeTimeLabelMap[req.SigningScheme]
if !ok {
return &signature.InvalidSignRequestError{Msg: "signing scheme: require notary.x509 or notary.x509.signingAuthority"}
}
protected[signingTimeLabel] = req.SigningTime.Unix()
signingTimeCBOR, err := encMode.Marshal(req.SigningTime)
patrickzheng200 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return &signature.InvalidSignRequestError{Msg: err.Error()}
}
protected[signingTimeLabel] = cbor.RawMessage(signingTimeCBOR)
if signingTimeLabel == headerLabelAuthenticSigningTime {
crit = append(crit, headerLabelAuthenticSigningTime)
}

// expiry
if !req.Expiry.IsZero() {
crit = append(crit, headerLabelExpiry)
protected[headerLabelExpiry] = req.Expiry.Unix()
expiryCBOR, err := encMode.Marshal(req.Expiry)
if err != nil {
return &signature.InvalidSignRequestError{Msg: err.Error()}
}
protected[headerLabelExpiry] = cbor.RawMessage(expiryCBOR)
}

// extended attributes
Expand Down Expand Up @@ -440,7 +470,7 @@ func generateUnprotectedHeaders(req *signature.SignRequest, signer signer, unpro

// parseProtectedHeaders parses COSE envelope's protected headers and
// populates signature.SignerInfo.
func parseProtectedHeaders(protected cose.ProtectedHeader, signerInfo *signature.SignerInfo) error {
func parseProtectedHeaders(protected cose.ProtectedHeader, signerInfo *signature.SignerInfo, isSign bool) error {
// validate critical headers and return extendedAttributeKeys
extendedAttributeKeys, err := validateCritHeaders(protected)
if err != nil {
Expand All @@ -466,25 +496,47 @@ func parseProtectedHeaders(protected cose.ProtectedHeader, signerInfo *signature
signingScheme := signature.SigningScheme(signingSchemeString)
signerInfo.SignedAttributes.SigningScheme = signingScheme

// decode Tag1 Datetime CBOR object into time.Time
decMode, err := decOpts.DecMode()
if err != nil {
return &signature.InvalidSignRequestError{Msg: err.Error()}
}
patrickzheng200 marked this conversation as resolved.
Show resolved Hide resolved

// populate signerInfo.SignedAttributes.SigningTime
signingTimeLabel, ok := signingSchemeTimeLabelMap[signingScheme]
if !ok {
return &signature.InvalidSignatureError{Msg: "unsupported signingScheme: " + signingSchemeString}
}
signingTime, ok := protected[signingTimeLabel].(int64)
if !ok {
return &signature.InvalidSignatureError{Msg: "invalid signingTime under signing scheme: " + signingSchemeString}
var signingTime time.Time
if isSign {
patrickzheng200 marked this conversation as resolved.
Show resolved Hide resolved
raw, ok := protected[signingTimeLabel].(cbor.RawMessage)
if !ok {
return &signature.InvalidSignatureError{Msg: "in Sign, protected[signingTimeLabel] requires to be cbor.RawMessage"}
}
decMode.Unmarshal([]byte(raw), &signingTime)
} else {
signingTime, err = parseTime(protected[signingTimeLabel])
if err != nil {
return &signature.InvalidSignatureError{Msg: "invalid signingTime under signing scheme: " + signingSchemeString}
}
}
signerInfo.SignedAttributes.SigningTime = time.Unix(signingTime, 0)
signerInfo.SignedAttributes.SigningTime = signingTime

// populate signerInfo.SignedAttributes.Expiry
if exp, ok := protected[headerLabelExpiry]; ok {
expiry, ok := exp.(int64)
var expiry time.Time
if isSign {
raw, ok := protected[headerLabelExpiry].(cbor.RawMessage)
if !ok {
return &signature.InvalidSignatureError{Msg: "expiry requires int64 type"}
return &signature.InvalidSignatureError{Msg: "in Sign, protected[headerLabelExpiry] requires to be cbor.RawMessage"}
}
decMode.Unmarshal([]byte(raw), expiry)
} else {
expiry, err = parseTime(protected[headerLabelExpiry])
if err != nil {
return &signature.InvalidSignatureError{Msg: "invalid expiry"}
}
signerInfo.SignedAttributes.Expiry = time.Unix(expiry, 0)
}
signerInfo.SignedAttributes.Expiry = expiry

// populate signerInfo.SignedAttributes.ExtendedAttributes
signerInfo.SignedAttributes.ExtendedAttributes, err = generateExtendedAttributes(extendedAttributeKeys, protected)
Expand Down Expand Up @@ -569,3 +621,15 @@ func contains(s []interface{}, e interface{}) bool {
}
return false
}

// parseTime parses time values from cose.ProtectedHeader
// in go-cose, CBOR times (tag 1) decode to time.Time.
//
// For more details: https://github.com/fxamacker/cbor/blob/7704fa5efaf3ef4ac35aff38f50f6ff567793072/decode.go#L52
func parseTime(timeValue interface{}) (time.Time, error) {
t, ok := timeValue.(time.Time)
if !ok {
return time.Time{}, errors.New("invalid date/time type")
}
return t, nil
}
69 changes: 66 additions & 3 deletions signature/cose/envelope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"github.com/fxamacker/cbor/v2"
"github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-core-go/signature/signaturetest"
"github.com/notaryproject/notation-core-go/testhelper"
Expand Down Expand Up @@ -219,6 +220,19 @@ func TestSignErrors(t *testing.T) {
}
})

t.Run("when encOpts.TimeTag is invalid", func(t *testing.T) {
signRequest, err := getSignRequest()
if err != nil {
t.Fatalf("getSignRequest() failed. Error = %v", err)
}
encOpts.TimeTag = 3
_, err = env.Sign(signRequest)
expected := errors.New("cbor: invalid TimeTag 3")
if !isErrEqual(expected, err) {
t.Fatalf("Sign() expects error: %v, but got: %v.", expected, err)
}
})

t.Run("when an extended signed attribute already exists in the protected header", func(t *testing.T) {
signRequest, err := getSignRequest()
if err != nil {
Expand Down Expand Up @@ -538,7 +552,34 @@ func TestSignerInfoErrors(t *testing.T) {
}
})

t.Run("when COSE envelope protected header has invalid signingTime", func(t *testing.T) {
t.Run("when decOpts.TimeTag is invalid", func(t *testing.T) {
env, err := getVerifyCOSE("notary.x509", signature.KeyTypeRSA, 3072)
if err != nil {
t.Fatalf("getVerifyCOSE() failed. Error = %s", err)
}
decOpts.TimeTag = 4
_, err = env.Content()
expected := errors.New("cbor: invalid TimeTag 4")
if !isErrEqual(expected, err) {
t.Fatalf("Content() expects error: %v, but got: %v.", expected, err)
}
})

t.Run("when COSE envelope protected header has invalid signingTime in sign", func(t *testing.T) {
env, err := getVerifyCOSE("notary.x509", signature.KeyTypeRSA, 3072)
if err != nil {
t.Fatalf("getVerifyCOSE() failed. Error = %s", err)
}
env.base.Headers.Protected[headerLabelSigningTime] = "invalid"
env.isSign = true
_, err = env.Content()
expected := errors.New("in Sign, protected[signingTimeLabel] requires to be cbor.RawMessage")
if !isErrEqual(expected, err) {
t.Fatalf("Content() expects error: %v, but got: %v.", expected, err)
}
})

t.Run("when COSE envelope protected header has invalid signingTime in verify", func(t *testing.T) {
env, err := getVerifyCOSE("notary.x509", signature.KeyTypeRSA, 3072)
if err != nil {
t.Fatalf("getVerifyCOSE() failed. Error = %s", err)
Expand All @@ -551,14 +592,29 @@ func TestSignerInfoErrors(t *testing.T) {
}
})

t.Run("when COSE envelope protected header has invalid expiry", func(t *testing.T) {
t.Run("when COSE envelope protected header has invalid expiry in sign", func(t *testing.T) {
env, err := getVerifyCOSE("notary.x509", signature.KeyTypeRSA, 3072)
if err != nil {
t.Fatalf("getVerifyCOSE() failed. Error = %s", err)
}
env.base.Headers.Protected[headerLabelSigningTime] = cbor.RawMessage{}
env.base.Headers.Protected[headerLabelExpiry] = "invalid"
env.isSign = true
_, err = env.Content()
expected := errors.New("in Sign, protected[headerLabelExpiry] requires to be cbor.RawMessage")
if !isErrEqual(expected, err) {
t.Fatalf("Content() expects error: %v, but got: %v.", expected, err)
}
})

t.Run("when COSE envelope protected header has invalid expiry in verify", func(t *testing.T) {
env, err := getVerifyCOSE("notary.x509", signature.KeyTypeRSA, 3072)
if err != nil {
t.Fatalf("getVerifyCOSE() failed. Error = %s", err)
}
env.base.Headers.Protected[headerLabelExpiry] = "invalid"
_, err = env.Content()
expected := errors.New("expiry requires int64 type")
expected := errors.New("invalid expiry")
if !isErrEqual(expected, err) {
t.Fatalf("Content() expects error: %v, but got: %v.", expected, err)
}
Expand Down Expand Up @@ -667,6 +723,10 @@ func TestSignAndParseVerify(t *testing.T) {
}

func newSignRequest(signingScheme string, keyType signature.KeyType, size int) (*signature.SignRequest, error) {
encOpts = cbor.EncOptions{
Time: cbor.TimeUnix,
TimeTag: cbor.EncTagRequired,
}
signer, err := signaturetest.GetTestLocalSigner(keyType, size)
if err != nil {
return nil, err
Expand All @@ -693,6 +753,9 @@ func getSignRequest() (*signature.SignRequest, error) {
}

func getVerifyCOSE(signingScheme string, keyType signature.KeyType, size int) (envelope, error) {
decOpts = cbor.DecOptions{
TimeTag: cbor.DecTagRequired,
}
signRequest, err := newSignRequest(signingScheme, keyType, size)
if err != nil {
return createNewEnv(nil), err
Expand Down
Loading