Beispiel #1
0
// put stores the manifest in the repository, if not already present. Any
// updated signatures will be stored, as well.
func (rs *revisionStore) put(ctx context.Context, sm *schema1.SignedManifest) (distribution.Descriptor, error) {
	// Resolve the payload in the manifest.
	payload, err := sm.Payload()
	if err != nil {
		return distribution.Descriptor{}, err
	}

	// Digest and store the manifest payload in the blob store.
	revision, err := rs.blobStore.Put(ctx, schema1.ManifestMediaType, payload)
	if err != nil {
		context.GetLogger(ctx).Errorf("error putting payload into blobstore: %v", err)
		return distribution.Descriptor{}, err
	}

	// Link the revision into the repository.
	if err := rs.blobStore.linkBlob(ctx, revision); err != nil {
		return distribution.Descriptor{}, err
	}

	// Grab each json signature and store them.
	signatures, err := sm.Signatures()
	if err != nil {
		return distribution.Descriptor{}, err
	}

	if err := rs.repository.Signatures().Put(revision.Digest, signatures...); err != nil {
		return distribution.Descriptor{}, err
	}

	return revision, nil
}
Beispiel #2
0
func schema1ToImage(manifest *schema1.SignedManifest, d digest.Digest) (*api.Image, error) {
	if len(manifest.History) == 0 {
		return nil, fmt.Errorf("image has no v1Compatibility history and cannot be used")
	}
	dockerImage, err := unmarshalDockerImage([]byte(manifest.History[0].V1Compatibility))
	if err != nil {
		return nil, err
	}
	mediatype, payload, err := manifest.Payload()
	if err != nil {
		return nil, err
	}

	if len(d) > 0 {
		dockerImage.ID = d.String()
	} else {
		dockerImage.ID = digest.FromBytes(manifest.Canonical).String()
	}
	image := &api.Image{
		ObjectMeta: kapi.ObjectMeta{
			Name: dockerImage.ID,
		},
		DockerImageMetadata:          *dockerImage,
		DockerImageManifest:          string(payload),
		DockerImageManifestMediaType: mediatype,
		DockerImageMetadataVersion:   "1.0",
	}

	return image, nil
}
Beispiel #3
0
func digestFromManifest(m *schema1.SignedManifest) (digest.Digest, error) {
	payload, err := m.Payload()
	if err != nil {
		return "", err
	}
	manifestDigest, err := digest.FromBytes(payload)
	return manifestDigest, 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
// content, leaving trust policies of that content up to consumers.
func (ms *signedManifestHandler) verifyManifest(ctx context.Context, mnfst schema1.SignedManifest, skipDependencyVerification bool) error {
	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)))
	}

	if _, err := schema1.Verify(&mnfst); err != nil {
		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 {
		for _, fsLayer := range mnfst.References() {
			_, 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
}
Beispiel #5
0
func digestFromManifest(m *schema1.SignedManifest, localName string) (digest.Digest, int, error) {
	payload, err := m.Payload()
	if err != nil {
		// If this failed, the signatures section was corrupted
		// or missing. Treat the entire manifest as the payload.
		payload = m.Raw
	}
	manifestDigest, err := digest.FromBytes(payload)
	if err != nil {
		logrus.Infof("Could not compute manifest digest for %s:%s : %v", localName, m.Tag, err)
	}
	return manifestDigest, len(payload), nil
}
func manifestDigest(sm *schema1.SignedManifest) (digest.Digest, error) {
	payload, err := sm.Payload()
	if err != nil {
		return "", err

	}

	dgst, err := digest.FromBytes(payload)
	if err != nil {
		return "", err
	}

	return dgst, nil
}
Beispiel #7
0
// signedManifestFillImageMetadata fills a given image with metadata. It also corrects layer sizes with blob sizes. Newer
// Docker client versions don't set layer sizes in the manifest at all. Origin master needs correct layer
// sizes for proper image quota support. That's why we need to fill the metadata in the registry.
func (r *repository) signedManifestFillImageMetadata(manifest *schema1.SignedManifest, image *imageapi.Image) error {
	signatures, err := manifest.Signatures()
	if err != nil {
		return err
	}

	for _, signDigest := range signatures {
		image.DockerImageSignatures = append(image.DockerImageSignatures, signDigest)
	}

	if err := imageapi.ImageWithMetadata(image); err != nil {
		return err
	}

	refs := manifest.References()

	blobSet := sets.NewString()
	image.DockerImageMetadata.Size = int64(0)

	blobs := r.Blobs(r.ctx)
	for i := range image.DockerImageLayers {
		layer := &image.DockerImageLayers[i]
		// DockerImageLayers represents manifest.Manifest.FSLayers in reversed order
		desc, err := blobs.Stat(r.ctx, refs[len(image.DockerImageLayers)-i-1].Digest)
		if err != nil {
			context.GetLogger(r.ctx).Errorf("failed to stat blobs %s of image %s", layer.Name, image.DockerImageReference)
			return err
		}
		if layer.MediaType == "" {
			if desc.MediaType != "" {
				layer.MediaType = desc.MediaType
			} else {
				layer.MediaType = schema1.MediaTypeManifestLayer
			}
		}
		layer.LayerSize = desc.Size
		// count empty layer just once (empty layer may actually have non-zero size)
		if !blobSet.Has(layer.Name) {
			image.DockerImageMetadata.Size += desc.Size
			blobSet.Insert(layer.Name)
		}
	}
	if len(image.DockerImageConfig) > 0 && !blobSet.Has(image.DockerImageMetadata.ID) {
		blobSet.Insert(image.DockerImageMetadata.ID)
		image.DockerImageMetadata.Size += int64(len(image.DockerImageConfig))
	}

	return nil
}
Beispiel #8
0
func verifyManifest(signedManifest *schema1.SignedManifest, tag string) (m *schema1.Manifest, err error) {
	// If pull by digest, then verify the manifest digest. NOTE: It is
	// important to do this first, before any other content validation. If the
	// digest cannot be verified, don't even bother with those other things.
	if manifestDigest, err := digest.ParseDigest(tag); err == nil {
		verifier, err := digest.NewDigestVerifier(manifestDigest)
		if err != nil {
			return nil, err
		}
		payload, err := signedManifest.Payload()
		if err != nil {
			// If this failed, the signatures section was corrupted
			// or missing. Treat the entire manifest as the payload.
			payload = signedManifest.Raw
		}
		if _, err := verifier.Write(payload); err != nil {
			return nil, err
		}
		if !verifier.Verified() {
			err := fmt.Errorf("image verification failed for digest %s", manifestDigest)
			logrus.Error(err)
			return nil, err
		}

		var verifiedManifest schema1.Manifest
		if err = json.Unmarshal(payload, &verifiedManifest); err != nil {
			return nil, err
		}
		m = &verifiedManifest
	} else {
		m = &signedManifest.Manifest
	}

	if m.SchemaVersion != 1 {
		return nil, fmt.Errorf("unsupported schema version %d for tag %q", m.SchemaVersion, tag)
	}
	if len(m.FSLayers) != len(m.History) {
		return nil, fmt.Errorf("length of history not equal to number of layers for tag %q", tag)
	}
	if len(m.FSLayers) == 0 {
		return nil, fmt.Errorf("no FSLayers in manifest for tag %q", tag)
	}
	return m, nil
}
Beispiel #9
0
// ManifestMatchesImage returns true if the provided manifest matches the name of the image.
func ManifestMatchesImage(image *Image, newManifest []byte) (bool, error) {
	dgst, err := digest.ParseDigest(image.Name)
	if err != nil {
		return false, err
	}
	v, err := digest.NewDigestVerifier(dgst)
	if err != nil {
		return false, err
	}
	sm := schema1.SignedManifest{Raw: newManifest}
	raw, err := sm.Payload()
	if err != nil {
		return false, err
	}
	if _, err := v.Write(raw); err != nil {
		return false, err
	}
	return v.Verified(), nil
}
Beispiel #10
0
// digestManifest takes a digest of the given manifest. This belongs somewhere
// better but we'll wait for a refactoring cycle to find that real somewhere.
func digestManifest(ctx context.Context, sm *schema1.SignedManifest) (digest.Digest, error) {
	p, err := sm.Payload()
	if err != nil {
		if !strings.Contains(err.Error(), "missing signature key") {
			ctxu.GetLogger(ctx).Errorf("error getting manifest payload: %v", err)
			return "", err
		}

		// NOTE(stevvooe): There are no signatures but we still have a
		// payload. The request will fail later but this is not the
		// responsibility of this part of the code.
		p = sm.Raw
	}

	dgst, err := digest.FromBytes(p)
	if err != nil {
		ctxu.GetLogger(ctx).Errorf("error digesting manifest: %v", err)
		return "", err
	}

	return dgst, err
}
func (registry *Registry) PutManifest(repository, reference string, signedManifest *manifestV1.SignedManifest) error {
	url := registry.url("/v2/%s/manifests/%s", repository, reference)
	registry.Logf("registry.manifest.put url=%s repository=%s reference=%s", url, repository, reference)

	body, err := signedManifest.MarshalJSON()
	if err != nil {
		return err
	}

	buffer := bytes.NewBuffer(body)
	req, err := http.NewRequest("PUT", url, buffer)
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", manifestV1.MediaTypeManifest)
	resp, err := registry.Client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	return err
}
Beispiel #12
0
func (b *bridge) createManifestEvent(action string, repo string, sm *schema1.SignedManifest) (*Event, error) {
	event := b.createEvent(action)
	event.Target.MediaType = schema1.ManifestMediaType
	event.Target.Repository = repo

	p, err := sm.Payload()
	if err != nil {
		return nil, err
	}

	event.Target.Length = int64(len(p))
	event.Target.Size = int64(len(p))
	event.Target.Digest, err = digest.FromBytes(p)
	if err != nil {
		return nil, err
	}

	event.Target.URL, err = b.ub.BuildManifestURL(sm.Name, event.Target.Digest.String())
	if err != nil {
		return nil, err
	}

	return event, nil
}
Beispiel #13
0
func schema1ToImage(manifest *schema1.SignedManifest, d digest.Digest) (*api.Image, error) {
	if len(manifest.History) == 0 {
		return nil, fmt.Errorf("image has no v1Compatibility history and cannot be used")
	}
	dockerImage, err := unmarshalDockerImage([]byte(manifest.History[0].V1Compatibility))
	if err != nil {
		return nil, err
	}
	if len(d) > 0 {
		dockerImage.ID = d.String()
	} else {
		if p, err := manifest.Payload(); err == nil {
			d, err := digest.FromBytes(p)
			if err != nil {
				return nil, fmt.Errorf("unable to create digest from image payload: %v", err)
			}
			dockerImage.ID = d.String()
		} else {
			d, err := digest.FromBytes(manifest.Raw)
			if err != nil {
				return nil, fmt.Errorf("unable to create digest from image bytes: %v", err)
			}
			dockerImage.ID = d.String()
		}
	}
	image := &api.Image{
		ObjectMeta: kapi.ObjectMeta{
			Name: dockerImage.ID,
		},
		DockerImageMetadata:        *dockerImage,
		DockerImageManifest:        string(manifest.Raw),
		DockerImageMetadataVersion: "1.0",
	}

	return image, nil
}
Beispiel #14
0
// TestValidateManifest verifies the validateManifest function
func TestValidateManifest(t *testing.T) {
	expectedDigest := "sha256:02fee8c3220ba806531f606525eceb83f4feb654f62b207191b1c9209188dedd"
	expectedFSLayer0 := digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")

	// Good manifest

	goodManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/good_manifest")
	if err != nil {
		t.Fatal("error reading fixture:", err)
	}

	var goodSignedManifest schema1.SignedManifest
	err = json.Unmarshal(goodManifestBytes, &goodSignedManifest)
	if err != nil {
		t.Fatal("error unmarshaling manifest:", err)
	}

	verifiedManifest, err := verifyManifest(&goodSignedManifest, expectedDigest)
	if err != nil {
		t.Fatal("validateManifest failed:", err)
	}

	if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
		t.Fatal("unexpected FSLayer in good manifest")
	}

	// "Extra data" manifest

	extraDataManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/extra_data_manifest")
	if err != nil {
		t.Fatal("error reading fixture:", err)
	}

	var extraDataSignedManifest schema1.SignedManifest
	err = json.Unmarshal(extraDataManifestBytes, &extraDataSignedManifest)
	if err != nil {
		t.Fatal("error unmarshaling manifest:", err)
	}

	verifiedManifest, err = verifyManifest(&extraDataSignedManifest, expectedDigest)
	if err != nil {
		t.Fatal("validateManifest failed:", err)
	}

	if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
		t.Fatal("unexpected FSLayer in extra data manifest")
	}

	// Bad manifest

	badManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/bad_manifest")
	if err != nil {
		t.Fatal("error reading fixture:", err)
	}

	var badSignedManifest schema1.SignedManifest
	err = json.Unmarshal(badManifestBytes, &badSignedManifest)
	if err != nil {
		t.Fatal("error unmarshaling manifest:", err)
	}

	verifiedManifest, err = verifyManifest(&badSignedManifest, expectedDigest)
	if err == nil || !strings.HasPrefix(err.Error(), "image verification failed for digest") {
		t.Fatal("expected validateManifest to fail with digest error")
	}

	// Manifest with no signature

	expectedWholeFileDigest := "7ec3615a120efcdfc270e9c7ea4183330775a3e52a09e2efb194b9a7c18e5ff7"

	noSignatureManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/no_signature_manifest")
	if err != nil {
		t.Fatal("error reading fixture:", err)
	}

	var noSignatureSignedManifest schema1.SignedManifest
	noSignatureSignedManifest.Raw = noSignatureManifestBytes
	err = json.Unmarshal(noSignatureManifestBytes, &noSignatureSignedManifest.Manifest)
	if err != nil {
		t.Fatal("error unmarshaling manifest:", err)
	}

	verifiedManifest, err = verifyManifest(&noSignatureSignedManifest, expectedWholeFileDigest)
	if err != nil {
		t.Fatal("validateManifest failed:", err)
	}

	if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 {
		t.Fatal("unexpected FSLayer in no-signature manifest")
	}
}
// Put creates or updates the named manifest.
func (r *repository) Put(manifest *schema1.SignedManifest) error {
	// Resolve the payload in the manifest.
	payload, err := manifest.Payload()
	if err != nil {
		return err
	}

	// Calculate digest
	dgst, err := digest.FromBytes(payload)
	if err != nil {
		return err
	}

	// Upload to openshift
	ism := imageapi.ImageStreamMapping{
		ObjectMeta: kapi.ObjectMeta{
			Namespace: r.namespace,
			Name:      r.name,
		},
		Tag: manifest.Tag,
		Image: imageapi.Image{
			ObjectMeta: kapi.ObjectMeta{
				Name: dgst.String(),
				Annotations: map[string]string{
					imageapi.ManagedByOpenShiftAnnotation: "true",
				},
			},
			DockerImageReference: fmt.Sprintf("%s/%s/%s@%s", r.registryAddr, r.namespace, r.name, dgst.String()),
			DockerImageManifest:  string(payload),
		},
	}

	if err := r.registryClient.ImageStreamMappings(r.namespace).Create(&ism); err != nil {
		// if the error was that the image stream wasn't found, try to auto provision it
		statusErr, ok := err.(*kerrors.StatusError)
		if !ok {
			context.GetLogger(r.ctx).Errorf("Error creating ImageStreamMapping: %s", err)
			return err
		}

		status := statusErr.ErrStatus
		if status.Code != http.StatusNotFound || status.Details.Kind != "imageStream" || status.Details.Name != r.name {
			context.GetLogger(r.ctx).Errorf("Error creating ImageStreamMapping: %s", err)
			return err
		}

		stream := imageapi.ImageStream{
			ObjectMeta: kapi.ObjectMeta{
				Name: r.name,
			},
		}

		client, ok := UserClientFrom(r.ctx)
		if !ok {
			context.GetLogger(r.ctx).Errorf("Error creating user client to auto provision image stream: Origin user client unavailable")
			return statusErr
		}

		if _, err := client.ImageStreams(r.namespace).Create(&stream); err != nil {
			context.GetLogger(r.ctx).Errorf("Error auto provisioning image stream: %s", err)
			return statusErr
		}

		// try to create the ISM again
		if err := r.registryClient.ImageStreamMappings(r.namespace).Create(&ism); err != nil {
			context.GetLogger(r.ctx).Errorf("Error creating image stream mapping: %s", err)
			return err
		}
	}

	// Grab each json signature and store them.
	signatures, err := manifest.Signatures()
	if err != nil {
		return err
	}

	for _, signature := range signatures {
		if err := r.Signatures().Put(dgst, signature); err != nil {
			context.GetLogger(r.ctx).Errorf("Error storing signature: %s", err)
			return err
		}
	}

	return nil
}
Beispiel #16
0
func outputManifestFor(target string) {
	var pkey trust.PrivateKey

	if key != "" {
		var err error
		pkey, err = trust.LoadKeyFile(key)
		if err != nil {
			fmt.Printf("error loading key: %s\n", err.Error())
			return
		}
	}

	if verbose {
		fmt.Errorf("signing with: %s\n", pkey.KeyID())
	}

	f, err := os.Open(target)
	if err != nil {
		fmt.Printf("error opening file: %s\n", err.Error())
		return
	}

	defer func() {
		if err := f.Close(); err != nil {
			panic(err)
		}
	}()

	var (
		repo, tag string
	)
	layers := LayerMap{}
	t := tar.NewReader(bufio.NewReader(f))
	for {
		hdr, err := t.Next()
		if err == io.EOF {
			break
		}

		if strings.HasSuffix(hdr.Name, "layer.tar") {
			id := getLayerPrefix(hdr.Name)
			sum, _ := blobSumLayer(t)
			if _, ok := layers[id]; !ok {
				layers[id] = &Layer{Id: id}
			} else {
				layers[id].BlobSum = sum
			}
		}

		if strings.HasSuffix(hdr.Name, "json") {
			data, _ := ioutil.ReadAll(t)
			parent, id, _ := getLayerInfo(data)
			if _, ok := layers[id]; !ok {
				layers[id] = &Layer{Id: id, Parent: parent}
			} else {
				layers[id].Parent = parent
			}

			var img image.Image
			json.Unmarshal(data, &img)
			b, _ := json.Marshal(img)
			layers[id].Data = string(b) + "\n"
		}

		if hdr.Name == "repositories" {
			r, _ := ioutil.ReadAll(t)
			var raw map[string]interface{}
			if err := json.Unmarshal(r, &raw); err != nil {
				return
			}

			repo, tag = getRepoInfo(raw)
			if !strings.Contains(repo, "/") {
				repo = "library/" + repo
			}
		}
	}

	m := manifest.Manifest{
		Versioned: versioned.Versioned{
			SchemaVersion: 1,
		},
		Name: repo, Tag: tag, Architecture: "amd64"}

	ll := getLayersFromMap(layers)
	for _, l := range getLayersInOrder(ll) {
		m.FSLayers = append(m.FSLayers, manifest.FSLayer{BlobSum: l.BlobSum})
		m.History = append(m.History, manifest.History{V1Compatibility: l.Data})
	}

	var x []byte
	if pkey != nil {
		var sm *manifest.SignedManifest
		sm, err = manifest.Sign(&m, pkey)
		x, err = sm.MarshalJSON()
	} else {
		x, err = json.MarshalIndent(m, "", "   ")
	}

	if print_digest {
		dgstr, _ := digest.FromBytes(x)
		fmt.Println(string(dgstr))
	}

	fmt.Println(string(x))
}
Beispiel #17
0
func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, manifestArgs) {
	imageName := args.imageName
	tag := "thetag"

	manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
	if err != nil {
		t.Fatalf("unexpected error getting manifest url: %v", err)
	}

	// -----------------------------
	// Attempt to fetch the manifest
	resp, err := http.Get(manifestURL)
	if err != nil {
		t.Fatalf("unexpected error getting manifest: %v", err)
	}
	defer resp.Body.Close()

	checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
	checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)

	tagsURL, err := env.builder.BuildTagsURL(imageName)
	if err != nil {
		t.Fatalf("unexpected error building tags url: %v", err)
	}

	resp, err = http.Get(tagsURL)
	if err != nil {
		t.Fatalf("unexpected error getting unknown tags: %v", err)
	}
	defer resp.Body.Close()

	// Check that we get an unknown repository error when asking for tags
	checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
	checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)

	// --------------------------------
	// Attempt to push unsigned manifest with missing layers
	unsignedManifest := &schema1.Manifest{
		Versioned: manifest.Versioned{
			SchemaVersion: 1,
		},
		Name: imageName,
		Tag:  tag,
		FSLayers: []schema1.FSLayer{
			{
				BlobSum: "asdf",
			},
			{
				BlobSum: "qwer",
			},
		},
		History: []schema1.History{
			{
				V1Compatibility: "",
			},
			{
				V1Compatibility: "",
			},
		},
	}

	resp = putManifest(t, "putting unsigned manifest", manifestURL, unsignedManifest)
	defer resp.Body.Close()
	checkResponse(t, "putting unsigned manifest", resp, http.StatusBadRequest)
	_, p, counts := checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeManifestInvalid)

	expectedCounts := map[errcode.ErrorCode]int{
		v2.ErrorCodeManifestInvalid: 1,
	}

	if !reflect.DeepEqual(counts, expectedCounts) {
		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
	}

	// sign the manifest and still get some interesting errors.
	sm, err := schema1.Sign(unsignedManifest, env.pk)
	if err != nil {
		t.Fatalf("error signing manifest: %v", err)
	}

	resp = putManifest(t, "putting signed manifest with errors", manifestURL, sm)
	defer resp.Body.Close()
	checkResponse(t, "putting signed manifest with errors", resp, http.StatusBadRequest)
	_, p, counts = checkBodyHasErrorCodes(t, "putting signed manifest with errors", resp,
		v2.ErrorCodeManifestBlobUnknown, v2.ErrorCodeDigestInvalid)

	expectedCounts = map[errcode.ErrorCode]int{
		v2.ErrorCodeManifestBlobUnknown: 2,
		v2.ErrorCodeDigestInvalid:       2,
	}

	if !reflect.DeepEqual(counts, expectedCounts) {
		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
	}

	// TODO(stevvooe): Add a test case where we take a mostly valid registry,
	// tamper with the content and ensure that we get a unverified manifest
	// error.

	// Push 2 random layers
	expectedLayers := make(map[digest.Digest]io.ReadSeeker)

	for i := range unsignedManifest.FSLayers {
		rs, dgstStr, err := testutil.CreateRandomTarFile()

		if err != nil {
			t.Fatalf("error creating random layer %d: %v", i, err)
		}
		dgst := digest.Digest(dgstStr)

		expectedLayers[dgst] = rs
		unsignedManifest.FSLayers[i].BlobSum = dgst

		uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
	}

	// -------------------
	// Push the signed manifest with all layers pushed.
	signedManifest, err := schema1.Sign(unsignedManifest, env.pk)
	if err != nil {
		t.Fatalf("unexpected error signing manifest: %v", err)
	}

	dgst := digest.FromBytes(signedManifest.Canonical)
	args.signedManifest = signedManifest
	args.dgst = dgst

	manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
	checkErr(t, err, "building manifest url")

	resp = putManifest(t, "putting signed manifest no error", manifestURL, signedManifest)
	checkResponse(t, "putting signed manifest no error", resp, http.StatusCreated)
	checkHeaders(t, resp, http.Header{
		"Location":              []string{manifestDigestURL},
		"Docker-Content-Digest": []string{dgst.String()},
	})

	// --------------------
	// Push by digest -- should get same result
	resp = putManifest(t, "putting signed manifest", manifestDigestURL, signedManifest)
	checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
	checkHeaders(t, resp, http.Header{
		"Location":              []string{manifestDigestURL},
		"Docker-Content-Digest": []string{dgst.String()},
	})

	// ------------------
	// Fetch by tag name
	resp, err = http.Get(manifestURL)
	if err != nil {
		t.Fatalf("unexpected error fetching manifest: %v", err)
	}
	defer resp.Body.Close()

	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
	checkHeaders(t, resp, http.Header{
		"Docker-Content-Digest": []string{dgst.String()},
		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
	})

	var fetchedManifest schema1.SignedManifest
	dec := json.NewDecoder(resp.Body)

	if err := dec.Decode(&fetchedManifest); err != nil {
		t.Fatalf("error decoding fetched manifest: %v", err)
	}

	if !bytes.Equal(fetchedManifest.Canonical, signedManifest.Canonical) {
		t.Fatalf("manifests do not match")
	}

	// ---------------
	// Fetch by digest
	resp, err = http.Get(manifestDigestURL)
	checkErr(t, err, "fetching manifest by digest")
	defer resp.Body.Close()

	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
	checkHeaders(t, resp, http.Header{
		"Docker-Content-Digest": []string{dgst.String()},
		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
	})

	var fetchedManifestByDigest schema1.SignedManifest
	dec = json.NewDecoder(resp.Body)
	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
		t.Fatalf("error decoding fetched manifest: %v", err)
	}

	if !bytes.Equal(fetchedManifestByDigest.Canonical, signedManifest.Canonical) {
		t.Fatalf("manifests do not match")
	}

	// check signature was roundtripped
	signatures, err := fetchedManifestByDigest.Signatures()
	if err != nil {
		t.Fatal(err)
	}

	if len(signatures) != 1 {
		t.Fatalf("expected 1 signature from manifest, got: %d", len(signatures))
	}

	// Re-sign, push and pull the same digest
	sm2, err := schema1.Sign(&fetchedManifestByDigest.Manifest, env.pk)
	if err != nil {
		t.Fatal(err)

	}

	resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, sm2)
	checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)

	resp, err = http.Get(manifestDigestURL)
	checkErr(t, err, "re-fetching manifest by digest")
	defer resp.Body.Close()

	checkResponse(t, "re-fetching uploaded manifest", resp, http.StatusOK)
	checkHeaders(t, resp, http.Header{
		"Docker-Content-Digest": []string{dgst.String()},
		"ETag":                  []string{fmt.Sprintf(`"%s"`, dgst)},
	})

	dec = json.NewDecoder(resp.Body)
	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
		t.Fatalf("error decoding fetched manifest: %v", err)
	}

	// check two signatures were roundtripped
	signatures, err = fetchedManifestByDigest.Signatures()
	if err != nil {
		t.Fatal(err)
	}

	if len(signatures) != 2 {
		t.Fatalf("expected 2 signature from manifest, got: %d", len(signatures))
	}

	// Get by name with etag, gives 304
	etag := resp.Header.Get("Etag")
	req, err := http.NewRequest("GET", manifestURL, nil)
	if err != nil {
		t.Fatalf("Error constructing request: %s", err)
	}
	req.Header.Set("If-None-Match", etag)
	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("Error constructing request: %s", err)
	}

	checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)

	// Get by digest with etag, gives 304
	req, err = http.NewRequest("GET", manifestDigestURL, nil)
	if err != nil {
		t.Fatalf("Error constructing request: %s", err)
	}
	req.Header.Set("If-None-Match", etag)
	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("Error constructing request: %s", err)
	}

	checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)

	// Ensure that the tag is listed.
	resp, err = http.Get(tagsURL)
	if err != nil {
		t.Fatalf("unexpected error getting unknown tags: %v", err)
	}
	defer resp.Body.Close()

	// Check that we get an unknown repository error when asking for tags
	checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK)
	dec = json.NewDecoder(resp.Body)

	var tagsResponse tagsAPIResponse

	if err := dec.Decode(&tagsResponse); err != nil {
		t.Fatalf("unexpected error decoding error response: %v", err)
	}

	if tagsResponse.Name != imageName {
		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
	}

	if len(tagsResponse.Tags) != 1 {
		t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
	}

	if tagsResponse.Tags[0] != tag {
		t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
	}

	// Attempt to put a manifest with mismatching FSLayer and History array cardinalities

	unsignedManifest.History = append(unsignedManifest.History, schema1.History{
		V1Compatibility: "",
	})
	invalidSigned, err := schema1.Sign(unsignedManifest, env.pk)
	if err != nil {
		t.Fatalf("error signing manifest")
	}

	resp = putManifest(t, "putting invalid signed manifest", manifestDigestURL, invalidSigned)
	checkResponse(t, "putting invalid signed manifest", resp, http.StatusBadRequest)

	return env, args
}