2015-12-15 02:19:34 +00:00
package storage
import (
2017-08-11 22:31:16 +00:00
"context"
2015-12-15 02:19:34 +00:00
"encoding/json"
"fmt"
2020-08-24 11:18:39 +00:00
"github.com/distribution/distribution/v3"
dcontext "github.com/distribution/distribution/v3/context"
2023-05-09 11:18:47 +00:00
"github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2020-08-24 11:18:39 +00:00
"github.com/distribution/distribution/v3/reference"
2015-12-15 02:19:34 +00:00
"github.com/docker/libtrust"
2016-12-17 00:28:34 +00:00
"github.com/opencontainers/go-digest"
2015-12-15 02:19:34 +00:00
)
// signedManifestHandler is a ManifestHandler that covers schema1 manifests. It
// can unmarshal and put schema1 manifests that have been signed by libtrust.
type signedManifestHandler struct {
2016-07-20 22:09:11 +00:00
repository distribution . Repository
schema1SigningKey libtrust . PrivateKey
blobStore distribution . BlobStore
ctx context . Context
2015-12-15 02:19:34 +00:00
}
var _ ManifestHandler = & signedManifestHandler { }
func ( ms * signedManifestHandler ) Unmarshal ( ctx context . Context , dgst digest . Digest , content [ ] byte ) ( distribution . Manifest , error ) {
2017-08-11 22:31:16 +00:00
dcontext . GetLogger ( ms . ctx ) . Debug ( "(*signedManifestHandler).Unmarshal" )
2016-02-10 23:20:39 +00:00
var (
signatures [ ] [ ] byte
err error
)
2015-12-15 02:19:34 +00:00
jsig , err := libtrust . NewJSONSignature ( content , signatures ... )
if err != nil {
return nil , err
}
2016-07-20 22:09:11 +00:00
if ms . schema1SigningKey != nil {
if err := jsig . Sign ( ms . schema1SigningKey ) ; err != nil {
2016-02-10 23:20:39 +00:00
return nil , err
}
}
2015-12-15 02:19:34 +00:00
// Extract the pretty JWS
raw , err := jsig . PrettySignature ( "signatures" )
if err != nil {
return nil , err
}
2023-05-09 11:18:47 +00:00
var sm schema1 . SignedManifest //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-12-15 02:19:34 +00:00
if err := json . Unmarshal ( raw , & sm ) ; err != nil {
return nil , err
}
return & sm , nil
}
func ( ms * signedManifestHandler ) Put ( ctx context . Context , manifest distribution . Manifest , skipDependencyVerification bool ) ( digest . Digest , error ) {
2017-08-11 22:31:16 +00:00
dcontext . GetLogger ( ms . ctx ) . Debug ( "(*signedManifestHandler).Put" )
2015-12-15 02:19:34 +00:00
2023-05-09 11:18:47 +00:00
sm , ok := manifest . ( * schema1 . SignedManifest ) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-12-15 02:19:34 +00:00
if ! ok {
return "" , fmt . Errorf ( "non-schema1 manifest put to signedManifestHandler: %T" , manifest )
}
if err := ms . verifyManifest ( ms . ctx , * sm , skipDependencyVerification ) ; err != nil {
return "" , err
}
2023-05-09 11:18:47 +00:00
mt := schema1 . MediaTypeManifest //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-12-15 02:19:34 +00:00
payload := sm . Canonical
revision , err := ms . blobStore . Put ( ctx , mt , payload )
if err != nil {
2017-08-11 22:31:16 +00:00
dcontext . GetLogger ( ctx ) . Errorf ( "error putting payload into blobstore: %v" , err )
2015-12-15 02:19:34 +00:00
return "" , err
}
return revision . Digest , nil
}
// verifyManifest ensures that the manifest content is valid from the
// perspective of the registry. It ensures that the signature is valid for the
// enclosed payload. As a policy, the registry only tries to store valid
2015-12-17 01:26:13 +00:00
// content, leaving trust policies of that content up to consumers.
2023-05-09 11:18:47 +00:00
func ( ms * signedManifestHandler ) verifyManifest ( ctx context . Context , mnfst schema1 . SignedManifest , skipDependencyVerification bool ) error { //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-12-15 02:19:34 +00:00
var errs distribution . ErrManifestVerification
if len ( mnfst . Name ) > reference . NameTotalLengthMax {
errs = append ( errs ,
distribution . ErrManifestNameInvalid {
Name : mnfst . Name ,
Reason : fmt . Errorf ( "manifest name must not be more than %v characters" , reference . NameTotalLengthMax ) ,
} )
}
if ! reference . NameRegexp . MatchString ( mnfst . Name ) {
errs = append ( errs ,
distribution . ErrManifestNameInvalid {
Name : mnfst . Name ,
Reason : fmt . Errorf ( "invalid manifest name format" ) ,
} )
}
if len ( mnfst . History ) != len ( mnfst . FSLayers ) {
errs = append ( errs , fmt . Errorf ( "mismatched history and fslayer cardinality %d != %d" ,
len ( mnfst . History ) , len ( mnfst . FSLayers ) ) )
}
2023-05-09 11:18:47 +00:00
if _ , err := schema1 . Verify ( & mnfst ) ; err != nil { //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-12-15 02:19:34 +00:00
switch err {
case libtrust . ErrMissingSignatureKey , libtrust . ErrInvalidJSONContent , libtrust . ErrMissingSignatureKey :
errs = append ( errs , distribution . ErrManifestUnverified { } )
default :
if err . Error ( ) == "invalid signature" { // TODO(stevvooe): This should be exported by libtrust
errs = append ( errs , distribution . ErrManifestUnverified { } )
} else {
errs = append ( errs , err )
}
}
}
if ! skipDependencyVerification {
2023-05-09 11:18:47 +00:00
for _ , fsLayer := range mnfst . References ( ) { //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility.
2015-12-15 02:19:34 +00:00
_ , err := ms . repository . Blobs ( ctx ) . Stat ( ctx , fsLayer . Digest )
if err != nil {
if err != distribution . ErrBlobUnknown {
errs = append ( errs , err )
}
// On error here, we always append unknown blob errors.
errs = append ( errs , distribution . ErrManifestBlobUnknown { Digest : fsLayer . Digest } )
}
}
}
if len ( errs ) != 0 {
return errs
}
return nil
}