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

Notation library baseline #5

Merged
merged 12 commits into from
Sep 15, 2021
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vscode
29 changes: 29 additions & 0 deletions example/basicauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved

import (
"net/http"
)

type transportWithBasicAuth struct {
base http.RoundTripper
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved
hostname string
username string
password string
}

// TransportWithBasicAuth returns the specified transport with basic auth
func TransportWithBasicAuth(tr http.RoundTripper, hostname, username, password string) http.RoundTripper {
return &transportWithBasicAuth{
base: tr,
hostname: hostname,
username: username,
password: password,
}
}

func (tr *transportWithBasicAuth) RoundTrip(req *http.Request) (*http.Response, error) {
if req.Host == tr.hostname {
req.SetBasicAuth(tr.username, tr.password)
}
return tr.base.RoundTrip(req)
}
123 changes: 123 additions & 0 deletions example/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package main
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved

import (
"context"
"crypto/x509"
"fmt"
"log"
"net/http"
"os"

"github.com/notaryproject/notation-go-lib"
"github.com/notaryproject/notation-go-lib/registry"
x509n "github.com/notaryproject/notation-go-lib/signature/x509"
"github.com/notaryproject/notation-go-lib/simple"
)

func main() {
if len(os.Args) < 4 {
fmt.Println("usage:", os.Args[0], "<key>", "<cert>", "<manifest>", "<references...>")
}

fmt.Println(">>> Initialize signing service")
signing, err := getSigningService(os.Args[1], os.Args[2])
if err != nil {
log.Fatal(err)
}

fmt.Println(">>> Initialize registry service")
ctx := context.Background()
client := getSignatureRegistry(
os.Getenv("notation_registry"),
os.Getenv("notation_username"),
os.Getenv("notation_password"),
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved
).Repository(ctx, os.Getenv("notation_repository"))

fmt.Println(">>> Initialize manifest")
references := os.Args[4:]
manifestPath := os.Args[3]
manifestDescriptor, err := registry.DescriptorFromFile(manifestPath)
if err != nil {
log.Fatal(err)
}
manifestDescriptor.MediaType = "application/vnd.docker.distribution.manifest.v2+json"
fmt.Println(manifestDescriptor)

fmt.Println(">>> Sign manifest")
sig, err := signing.Sign(ctx, manifestDescriptor, references...)
if err != nil {
log.Fatal(err)
}

fmt.Println(">>> Verify signature")
references, err = signing.Verify(ctx, manifestDescriptor, sig)
if err != nil {
log.Fatal(err)
}
fmt.Println(references)

fmt.Println(">>> Put signature")
signatureDescriptor, err := client.Put(ctx, sig)
if err != nil {
log.Fatal(err)
}
fmt.Println(signatureDescriptor.Digest)

fmt.Println(">>> Link signature")
artifactDescriptor, err := client.Link(ctx, manifestDescriptor, signatureDescriptor)
if err != nil {
log.Fatal(err)
}
fmt.Println(artifactDescriptor.Digest)

fmt.Println(">>> Lookup signatures")
signatureDigests, err := client.Lookup(ctx, manifestDescriptor.Digest)
if err != nil {
log.Fatal(err)
}
for _, signatureDigest := range signatureDigests {
fmt.Println("-", signatureDigest)
}

for _, signatureDigest := range signatureDigests {
fmt.Println(">>> Get signature:", signatureDigest)
sig, err := client.Get(ctx, signatureDigest)
if err != nil {
log.Println(err)
continue
}

fmt.Println(">>> Verify signature:", signatureDigest)
references, err = signing.Verify(ctx, manifestDescriptor, sig)
if err != nil {
log.Println(err)
continue
}
fmt.Println(references)
}
}

func getSigningService(keyPath, certPath string) (notation.SigningService, error) {
key, err := x509n.ReadPrivateKeyFile(keyPath)
if err != nil {
return nil, err
}
certs, err := x509n.ReadCertificateFile(certPath)
if err != nil {
return nil, err
}
rootCerts := x509.NewCertPool()
for _, cert := range certs {
rootCerts.AddCert(cert)
}
return simple.NewSigningService(key, certs, certs, rootCerts)
}

func getSignatureRegistry(name, username, password string) notation.SignatureRegistry {
plainHTTP := username == "" // for http access
tr := http.DefaultTransport
if !plainHTTP {
tr = TransportWithBasicAuth(tr, name, username, password)
}
return registry.NewClient(tr, name, plainHTTP)
}
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/notaryproject/notation-go-lib

go 1.17

require (
github.com/docker/go v1.5.1-1
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1
github.com/oras-project/artifacts-spec v0.0.0-20210827194259-6e52c5a2ed3d
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/docker/go v1.5.1-1 h1:hr4w35acWBPhGBXlzPoHpmZ/ygPjnmFVxGxxGnMyP7k=
github.com/docker/go v1.5.1-1/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/oras-project/artifacts-spec v0.0.0-20210827194259-6e52c5a2ed3d h1:fnJDGYyP6INkpdty1iJzXS3jI6n9RaLK0JN+glIPmsE=
github.com/oras-project/artifacts-spec v0.0.0-20210827194259-6e52c5a2ed3d/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc=
28 changes: 28 additions & 0 deletions registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package notation

import (
"context"

"github.com/opencontainers/go-digest"
oci "github.com/opencontainers/image-spec/specs-go/v1"
)

// SignatureRegistry provides signature repositories
type SignatureRegistry interface {
Repository(ctx context.Context, name string) SignatureRepository
}

// SignatureRepository provides a storage for signatures
type SignatureRepository interface {
// Lookup finds all signatures for the specified manifest
Lookup(ctx context.Context, manifestDigest digest.Digest) ([]digest.Digest, error)

// Get downloads the signature by the specified digest
Get(ctx context.Context, signatureDigest digest.Digest) ([]byte, error)

// Put uploads the signature to the registry
Put(ctx context.Context, signature []byte) (oci.Descriptor, error)

// Link creates an signature artifact linking the manifest and the signature
Link(ctx context.Context, manifest, signature oci.Descriptor) (oci.Descriptor, error)
}
40 changes: 40 additions & 0 deletions registry/descriptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package registry
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved

import (
"os"

"github.com/opencontainers/go-digest"
oci "github.com/opencontainers/image-spec/specs-go/v1"
)

// DescriptorFromBytes computes the basic descriptor from the given bytes
func DescriptorFromBytes(data []byte) oci.Descriptor {
return oci.Descriptor{
Digest: digest.FromBytes(data),
Size: int64(len(data)),
}
}

// DescriptorFromFile computes the basic descriptor from the file
func DescriptorFromFile(path string) (oci.Descriptor, error) {
file, err := os.Open(path)
if err != nil {
return oci.Descriptor{}, err
}
defer file.Close()

stat, err := file.Stat()
if err != nil {
return oci.Descriptor{}, err
}

digest, err := digest.FromReader(file)
if err != nil {
return oci.Descriptor{}, err
}

return oci.Descriptor{
Digest: digest,
Size: stat.Size(),
}, nil
}
28 changes: 28 additions & 0 deletions registry/io.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package registry

import (
"fmt"
"io"

"github.com/opencontainers/go-digest"
)

const maxReadLimit = 4 * 1024 * 1024

func readAllVerified(r io.Reader, expected digest.Digest) ([]byte, error) {
digester := expected.Algorithm().Digester()
content, err := io.ReadAll(io.TeeReader(
io.LimitReader(r, maxReadLimit),
digester.Hash(),
))
if err != nil {
return nil, err
}
if len(content) == maxReadLimit {
return nil, fmt.Errorf("reached max read limit %d", maxReadLimit)
}
if actual := digester.Digest(); actual != expected {
return nil, fmt.Errorf("mismatch digest: expect %v: got %v", expected, actual)
}
return content, nil
}
11 changes: 11 additions & 0 deletions registry/mediatype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package registry

const (
// ArtifactTypeNotation specifies the artifact type for a notation object.
ArtifactTypeNotation = "application/vnd.cncf.notary.v2"
)

const (
// MediaTypeNotarySignature specifies the media type for the notary signature.
MediaTypeNotarySignature = "application/vnd.cncf.notary.signature.v2+jwt"
)
35 changes: 35 additions & 0 deletions registry/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package registry

import (
"context"
"fmt"
"net/http"

"github.com/notaryproject/notation-go-lib"
)

type registry struct {
tr http.RoundTripper
base string
}

// NewClient creates a client to the remote registry
// for accessing the signatures.
func NewClient(tr http.RoundTripper, name string, plainHTTP bool) notation.SignatureRegistry {
scheme := "https"
if plainHTTP {
scheme = "http"
}
return &registry{
tr: tr,
base: fmt.Sprintf("%s://%s", scheme, name),
}
}

func (r *registry) Repository(ctx context.Context, name string) notation.SignatureRepository {
return &repository{
tr: r.tr,
base: r.base,
name: name,
}
}
Loading