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

Implement generate signature plugin workflow #42

Merged
merged 59 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
d0c0218
implement plugin manager
qmuntal Apr 26, 2022
2943a57
ignore symlinks
qmuntal Apr 26, 2022
c33c354
fix Manager.Command
qmuntal Apr 27, 2022
5bfff33
export constants
qmuntal Apr 27, 2022
c09b4a1
pr feedback
qmuntal Apr 28, 2022
e1f2d49
improve error message
qmuntal Apr 28, 2022
2cacb9f
move manager to its own package
qmuntal Apr 28, 2022
55fc95a
change metadata error messages
qmuntal Apr 28, 2022
f0636e1
improve the plugin manager interface
qmuntal Apr 29, 2022
b4e505d
implement plugin.Run
qmuntal Apr 30, 2022
295d0bc
add plugin signer
qmuntal May 2, 2022
e39e8ce
improve Manager.Run
qmuntal May 2, 2022
6307514
base64 encode payload
qmuntal May 2, 2022
d0874d9
fix supported algs
qmuntal May 2, 2022
15d7713
create envelope
qmuntal May 3, 2022
d1f6564
pr feedback
qmuntal May 3, 2022
74dc477
remove Command.Capability() and Command.NewResponse()
qmuntal May 3, 2022
f37ca5b
add DescribeKey command
qmuntal May 3, 2022
504ca16
remove command validation
qmuntal May 3, 2022
7beb5cd
plugin cleanup
qmuntal May 3, 2022
a3bdec6
fix compilation
qmuntal May 3, 2022
04da321
base64 encode signature
qmuntal May 3, 2022
8f78a94
ignore symlinked plugin directories
qmuntal May 4, 2022
bd10cfe
suuport plugin key spec
qmuntal May 4, 2022
ceb9b92
pass signing payload as ascii string
qmuntal May 4, 2022
26c6b91
fix rsa method
qmuntal May 4, 2022
3470557
define KeySpec
qmuntal May 4, 2022
4d98f6d
add signing tests
qmuntal May 5, 2022
01a45db
Bump actions/setup-go from 2 to 3 (#32)
dependabot[bot] Apr 27, 2022
8ea7512
Bump actions/cache from 2 to 3.0.1 (#31)
dependabot[bot] Apr 27, 2022
c6553f8
Bump github.com/golang-jwt/jwt/v4 from 4.3.0 to 4.4.1 (#29)
dependabot[bot] Apr 27, 2022
7891053
Merge branch 'main' of https://github.com/notaryproject/notation-go i…
qmuntal May 5, 2022
03b1a43
check extension ID length and add tests
qmuntal May 5, 2022
7bbec7e
fail on empty certificate chain
qmuntal May 5, 2022
c3265c3
create and use spec package
qmuntal May 5, 2022
f50b242
dedup notaryClaim
qmuntal May 5, 2022
c9afb7c
remove support for multiple signature envelope
qmuntal May 6, 2022
70877b2
move payload creation to where it's needed
qmuntal May 6, 2022
a4981c0
add missing json tags
qmuntal May 6, 2022
a2fff48
use correct header encoding
qmuntal May 6, 2022
eb16900
rename NewManager to New and add root param
qmuntal May 9, 2022
2a6e597
remove artifact type from descriptor
qmuntal May 9, 2022
a453d9e
dedup jwt.Token creation
qmuntal May 9, 2022
a284a1e
simpligy running plugin command
qmuntal May 9, 2022
c55f36f
update plugin spec
qmuntal May 11, 2022
92366fb
PR feedback
qmuntal May 11, 2022
fc5592f
check runner response type
qmuntal May 11, 2022
b2c02f5
remove spec/v1 directory
qmuntal May 12, 2022
b9246e9
Revert "remove support for multiple signature envelope"
qmuntal May 12, 2022
cbc16f9
don't check timestamp certificate
qmuntal May 12, 2022
5e2d8ca
define SignatureAlgorithm in spec
qmuntal May 12, 2022
a24ae78
Update plugin implementation with latest spec
qmuntal May 13, 2022
35b4fac
improve plugin error report
qmuntal May 14, 2022
8177f1c
add x5c comment
qmuntal May 18, 2022
979142f
simplify runner interface
qmuntal May 18, 2022
6fe2523
remove spec package
qmuntal May 18, 2022
ddf72d9
pr feedback
qmuntal May 18, 2022
cca70c9
Apply suggestions from code review
qmuntal May 18, 2022
07fe9b8
fix build
qmuntal May 18, 2022
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
91 changes: 91 additions & 0 deletions jws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package notation

const (
// MediaTypeJWSEnvelope describes the media type of the JWS envelope.
MediaTypeJWSEnvelope = "application/vnd.cncf.notary.v2.jws.v1"
)

// JWSPayload contains the set of claims used by Notary V2.
type JWSPayload struct {
// Private claim.
Subject Descriptor `json:"subject"`

// Identifies the number of seconds since Epoch at which the signature was issued.
IssuedAt int64 `json:"iat"`

// Identifies the number of seconds since Epoch at which the signature must not be considered valid.
ExpiresAt int64 `json:"exp,omitempty"`
}

// JWSProtectedHeader contains the set of protected headers.
type JWSProtectedHeader struct {
// Defines which algorithm was used to generate the signature.
Algorithm string `json:"alg"`

// Media type of the secured content (the payload).
ContentType string `json:"cty"`
}

// JWSUnprotectedHeader contains the set of unprotected headers.
type JWSUnprotectedHeader struct {
// RFC3161 time stamp token Base64-encoded.
TimeStampToken []byte `json:"timestamp,omitempty"`

// List of X.509 Base64-DER-encoded certificates
// as defined at https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6.
CertChain [][]byte `json:"x5c"`
}

// JWSEnvelope is the final signature envelope.
type JWSEnvelope struct {
// JWSPayload Base64URL-encoded.
Payload string `json:"payload"`

// JWSProtectedHeader Base64URL-encoded.
Protected string `json:"protected"`

// Signature metadata that is not integrity protected
Header JWSUnprotectedHeader `json:"header"`

// Base64URL-encoded signature.
Signature string `json:"signature"`
}

// JWS returns the JWS algorithm name.
func (s SignatureAlgorithm) JWS() string {
switch s {
case RSASSA_PSS_SHA_256:
return "PS256"
case RSASSA_PSS_SHA_384:
return "PS384"
case RSASSA_PSS_SHA_512:
return "PS512"
case ECDSA_SHA_256:
return "ES256"
case ECDSA_SHA_384:
return "ES384"
case ECDSA_SHA_512:
return "ES512"
}
return ""
}

// NewSignatureAlgorithmJWS returns the algorithm associated to alg.
// It returns an empty string if alg is not supported.
func NewSignatureAlgorithmJWS(alg string) SignatureAlgorithm {
switch alg {
case "PS256":
return RSASSA_PSS_SHA_256
case "PS384":
return RSASSA_PSS_SHA_384
case "PS512":
return RSASSA_PSS_SHA_512
case "ES256":
return ECDSA_SHA_256
case "ES384":
return ECDSA_SHA_384
case "ES512":
return ECDSA_SHA_512
}
return ""
}
95 changes: 87 additions & 8 deletions notation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package notation

import (
"context"
"crypto"
"crypto/x509"
"time"

Expand All @@ -11,16 +12,16 @@ import (

// Descriptor describes the content signed or to be signed.
type Descriptor struct {
// MediaType is the media type of the targeted content.
// The media type of the targeted content.
MediaType string `json:"mediaType"`

// Digest is the digest of the targeted content.
// The digest of the targeted content.
Digest digest.Digest `json:"digest"`

// Size specifies the size in bytes of the blob.
// Specifies the size in bytes of the blob.
Size int64 `json:"size"`

// Annotations contains optional user defined attributes.
// Contains optional user defined attributes.
Annotations map[string]string `json:"annotations,omitempty"`
}

Expand All @@ -42,11 +43,9 @@ type SignOptions struct {
// the certificates in the fetched timestamp signature.
// An empty list of `KeyUsages` in the verify options implies ExtKeyUsageTimeStamping.
TSAVerifyOptions x509.VerifyOptions
}

// Validate does basic validation on SignOptions.
func (opts SignOptions) Validate() error {
return nil
// Sets or overrides the plugin configuration.
PluginConfig map[string]string
}

// Signer is a generic interface for signing an artifact.
Expand Down Expand Up @@ -78,3 +77,83 @@ type Service interface {
Signer
Verifier
}

// KeySpec defines a key type and size.
type KeySpec string

// One of following supported specs
// https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md#algorithm-selection
const (
RSA_2048 KeySpec = "RSA_2048"
RSA_3072 KeySpec = "RSA_3072"
RSA_4096 KeySpec = "RSA_4096"
EC_256 KeySpec = "EC_256"
EC_384 KeySpec = "EC_384"
EC_512 KeySpec = "EC_512"
)

// SignatureAlgorithm returns the signing algorithm associated with KeyType k.
func (k KeySpec) SignatureAlgorithm() SignatureAlgorithm {
switch k {
case RSA_2048:
return RSASSA_PSS_SHA_256
case RSA_3072:
return RSASSA_PSS_SHA_384
case RSA_4096:
return RSASSA_PSS_SHA_512
case EC_256:
return ECDSA_SHA_256
case EC_384:
return ECDSA_SHA_384
case EC_512:
return ECDSA_SHA_512
}
return ""
}

// HashAlgorithm algorithm associated with the key spec.
type HashAlgorithm string

const (
SHA256 HashAlgorithm = "SHA_256"
SHA384 HashAlgorithm = "SHA_384"
SHA512 HashAlgorithm = "SHA_512"
)

// HashFunc returns the Hash associated k.
func (h HashAlgorithm) HashFunc() crypto.Hash {
switch h {
case SHA256:
return crypto.SHA256
case SHA384:
return crypto.SHA384
case SHA512:
return crypto.SHA512
}
return 0
}

// SignatureAlgorithm defines the supported signature algorithms.
type SignatureAlgorithm string

const (
RSASSA_PSS_SHA_256 SignatureAlgorithm = "RSASSA_PSS_SHA_256"
RSASSA_PSS_SHA_384 SignatureAlgorithm = "RSASSA_PSS_SHA_384"
RSASSA_PSS_SHA_512 SignatureAlgorithm = "RSASSA_PSS_SHA_512"
ECDSA_SHA_256 SignatureAlgorithm = "ECDSA_SHA_256"
ECDSA_SHA_384 SignatureAlgorithm = "ECDSA_SHA_384"
ECDSA_SHA_512 SignatureAlgorithm = "ECDSA_SHA_512"
)

// Hash returns the Hash associated s.
func (s SignatureAlgorithm) Hash() HashAlgorithm {
switch s {
case RSASSA_PSS_SHA_256, ECDSA_SHA_256:
return SHA256
case RSASSA_PSS_SHA_384, ECDSA_SHA_384:
return SHA384
case RSASSA_PSS_SHA_512, ECDSA_SHA_512:
return SHA512
}
return ""
}
18 changes: 17 additions & 1 deletion plugin/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ func (e RequestError) Unwrap() error {
return e.Err
}

func (e RequestError) Is(target error) bool {
if et, ok := target.(RequestError); ok {
if e.Code != et.Code {
return false
}
if e.Err == et.Err {
return true
}
return e.Err != nil && et.Err != nil && e.Err.Error() == et.Err.Error()
}
return false
}

func (e RequestError) MarshalJSON() ([]byte, error) {
var msg string
if e.Err != nil {
Expand All @@ -69,6 +82,9 @@ func (e *RequestError) UnmarshalJSON(data []byte) error {
if tmp.Code == "" && tmp.Message == "" && tmp.Metadata == nil {
return errors.New("incomplete json")
}
*e = RequestError{tmp.Code, errors.New(tmp.Message), tmp.Metadata}
*e = RequestError{Code: tmp.Code, Metadata: tmp.Metadata}
if tmp.Message != "" {
e.Err = errors.New(tmp.Message)
}
return nil
}
26 changes: 26 additions & 0 deletions plugin/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,29 @@ func TestRequestError_UnmarshalJSON(t *testing.T) {
})
}
}

func TestRequestError_Is(t *testing.T) {
type args struct {
target error
}
tests := []struct {
name string
e RequestError
args args
want bool
}{
{"nil", RequestError{}, args{nil}, false},
{"not same type", RequestError{Err: errors.New("foo")}, args{errors.New("foo")}, false},
{"only same code", RequestError{Code: ErrorCodeGeneric, Err: errors.New("foo")}, args{RequestError{Code: ErrorCodeGeneric, Err: errors.New("bar")}}, false},
{"only same message", RequestError{Code: ErrorCodeTimeout, Err: errors.New("foo")}, args{RequestError{Code: ErrorCodeGeneric, Err: errors.New("foo")}}, false},
{"same with nil message", RequestError{Code: ErrorCodeGeneric}, args{RequestError{Code: ErrorCodeGeneric}}, true},
{"same", RequestError{Code: ErrorCodeGeneric, Err: errors.New("foo")}, args{RequestError{Code: ErrorCodeGeneric, Err: errors.New("foo")}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.e.Is(tt.args.target); got != tt.want {
t.Errorf("RequestError.Is() = %v, want %v", got, tt.want)
}
})
}
}
19 changes: 16 additions & 3 deletions plugin/manager/integration_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package manager
package manager_test

import (
"context"
Expand All @@ -7,9 +7,11 @@ import (
"os/exec"
"path/filepath"
"reflect"
"runtime"
"testing"

"github.com/notaryproject/notation-go/plugin"
"github.com/notaryproject/notation-go/plugin/manager"
)

func preparePlugin(t *testing.T) string {
Expand Down Expand Up @@ -53,7 +55,7 @@ func TestIntegration(t *testing.T) {
t.Skip()
}
root := preparePlugin(t)
mgr := &Manager{rootedFS{os.DirFS(root), root}, execCommander{}}
mgr := manager.New(root)
p, err := mgr.Get(context.Background(), "foo")
if err != nil {
t.Fatal(err)
Expand All @@ -71,8 +73,19 @@ func TestIntegration(t *testing.T) {
if !reflect.DeepEqual(list[0].Metadata, p.Metadata) {
t.Errorf("Manager.List() got %v, want %v", list[0], p)
}
_, err = mgr.Run(context.Background(), "foo", plugin.CommandGetMetadata, nil)
r, err := mgr.Runner("foo")
if err != nil {
t.Fatal(err)
}
_, err = r.Run(context.Background(), plugin.GetMetadataRequest{})
if err != nil {
t.Fatal(err)
}
}

func addExeSuffix(s string) string {
if runtime.GOOS == "windows" {
s += ".exe"
}
return s
}
Loading