Skip to content

Commit

Permalink
Notation library baseline (#5)
Browse files Browse the repository at this point in the history
* notary library prototype
* update with latest artifact spec
* update with latest link API
* proper variable naming for plain http access
* refactor code to use latest spec
* safe read blob
* update registry to the latest spec
* update reference API
* update dependency
* update artifact type
* rename notary to notation
* update to latest referrer API
Signed-off-by: Shiwei Zhang <shizh@microsoft.com>
  • Loading branch information
shizhMSFT authored Sep 15, 2021
1 parent 6978124 commit 484c01c
Show file tree
Hide file tree
Showing 23 changed files with 1,181 additions and 0 deletions.
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

import (
"net/http"
)

type transportWithBasicAuth struct {
base http.RoundTripper
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

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"),
).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
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
)
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

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

0 comments on commit 484c01c

Please sign in to comment.