Exemple #1
0
// SignRoot signs the root, using all keys from the "root" role (i.e. currently trusted)
// as well as available keys used to sign the previous version, if the public part is
// carried in tr.Root.Keys and the private key is available (i.e. probably previously
// trusted keys, to allow rollover).  If there are any errors, attempt to put root
// back to the way it was (so version won't be incremented, for instance).
func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
	logrus.Debug("signing root...")

	// duplicate root and attempt to modify it rather than the existing root
	rootBytes, err := tr.Root.MarshalJSON()
	if err != nil {
		return nil, err
	}
	tempRoot := data.SignedRoot{}
	if err := json.Unmarshal(rootBytes, &tempRoot); err != nil {
		return nil, err
	}

	currRoot, err := tr.GetBaseRole(data.CanonicalRootRole)
	if err != nil {
		return nil, err
	}

	oldRootRoles := tr.getOldRootRoles()

	var latestSavedRole data.BaseRole
	rolesToSignWith := make([]data.BaseRole, 0, len(oldRootRoles))

	if len(oldRootRoles) > 0 {
		sort.Sort(oldRootRoles)
		for _, vRole := range oldRootRoles {
			rolesToSignWith = append(rolesToSignWith, vRole.BaseRole)
		}
		latest := rolesToSignWith[len(rolesToSignWith)-1]
		latestSavedRole = data.BaseRole{
			Name:      data.CanonicalRootRole,
			Threshold: latest.Threshold,
			Keys:      latest.Keys,
		}
	}

	// if the root role has changed and original role had not been saved as a previous role, save it now
	if !tr.originalRootRole.Equals(currRoot) && !tr.originalRootRole.Equals(latestSavedRole) {
		rolesToSignWith = append(rolesToSignWith, tr.originalRootRole)
		latestSavedRole = tr.originalRootRole

		versionName := oldRootVersionName(tempRoot.Signed.Version)
		tempRoot.Signed.Roles[versionName] = &data.RootRole{
			KeyIDs: latestSavedRole.ListKeyIDs(), Threshold: latestSavedRole.Threshold}

	}

	tempRoot.Signed.Expires = expires
	tempRoot.Signed.Version++

	// if the current role doesn't match with the latest saved role, save it
	if !currRoot.Equals(latestSavedRole) {
		rolesToSignWith = append(rolesToSignWith, currRoot)

		versionName := oldRootVersionName(tempRoot.Signed.Version)
		tempRoot.Signed.Roles[versionName] = &data.RootRole{
			KeyIDs: currRoot.ListKeyIDs(), Threshold: currRoot.Threshold}
	}

	signed, err := tempRoot.ToSigned()
	if err != nil {
		return nil, err
	}
	signed, err = tr.sign(signed, rolesToSignWith, tr.getOptionalRootKeys(rolesToSignWith))
	if err != nil {
		return nil, err
	}

	tr.Root = &tempRoot
	tr.Root.Signatures = signed.Signatures
	tr.originalRootRole = currRoot
	return signed, nil
}
Exemple #2
0
// checkRoot returns true if no rotation, or a valid
// rotation has taken place, and the threshold number of signatures
// are valid.
func checkRoot(oldRoot, newRoot *data.SignedRoot) error {
	rootRole := data.RoleName(data.CanonicalRootRole)
	targetsRole := data.RoleName(data.CanonicalTargetsRole)
	snapshotRole := data.RoleName(data.CanonicalSnapshotRole)
	timestampRole := data.RoleName(data.CanonicalTimestampRole)

	var oldRootRole *data.RootRole
	newRootRole, ok := newRoot.Signed.Roles[rootRole]
	if !ok {
		return errors.New("new root is missing role entry for root role")
	}

	oldThreshold := 1
	rotation := false
	oldKeys := map[string]data.PublicKey{}
	newKeys := map[string]data.PublicKey{}
	if oldRoot != nil {
		// check for matching root key IDs
		oldRootRole = oldRoot.Signed.Roles[rootRole]
		oldThreshold = oldRootRole.Threshold

		for _, kid := range oldRootRole.KeyIDs {
			k, ok := oldRoot.Signed.Keys[kid]
			if !ok {
				// if the key itself wasn't contained in the root
				// we're skipping it because it could never have
				// been used to validate this root.
				continue
			}
			oldKeys[kid] = data.NewPublicKey(k.Algorithm(), k.Public())
		}

		// super simple check for possible rotation
		rotation = len(oldKeys) != len(newRootRole.KeyIDs)
	}
	// if old and new had the same number of keys, iterate
	// to see if there's a difference.
	for _, kid := range newRootRole.KeyIDs {
		k, ok := newRoot.Signed.Keys[kid]
		if !ok {
			// if the key itself wasn't contained in the root
			// we're skipping it because it could never have
			// been used to validate this root.
			continue
		}
		newKeys[kid] = data.NewPublicKey(k.Algorithm(), k.Public())

		if oldRoot != nil {
			if _, ok := oldKeys[kid]; !ok {
				// if there is any difference in keys, a key rotation may have
				// occurred.
				rotation = true
			}
		}
	}
	newSigned, err := newRoot.ToSigned()
	if err != nil {
		return err
	}
	if rotation {
		err = signed.VerifyRoot(newSigned, oldThreshold, oldKeys)
		if err != nil {
			return fmt.Errorf("rotation detected and new root was not signed with at least %d old keys", oldThreshold)
		}
	}
	err = signed.VerifyRoot(newSigned, newRootRole.Threshold, newKeys)
	if err != nil {
		return err
	}
	root, err := data.RootFromSigned(newSigned)
	if err != nil {
		return err
	}
	// at a minimum, check the 4 required roles are present
	for _, r := range []string{rootRole, targetsRole, snapshotRole, timestampRole} {
		role, ok := root.Signed.Roles[r]
		if !ok {
			return fmt.Errorf("missing required %s role from root", r)
		}
		if role.Threshold < 1 {
			return fmt.Errorf("%s role has invalid threshold", r)
		}
		if len(role.KeyIDs) < role.Threshold {
			return fmt.Errorf("%s role has insufficient number of keys", r)
		}
	}
	return nil
}
Exemple #3
0
// checkRoot errors if an invalid rotation has taken place, if the
// threshold number of signatures is invalid, if there are an invalid
// number of roles and keys, or if the timestamp keys are invalid
func checkRoot(oldRoot, newRoot *data.SignedRoot, timestampKey data.PublicKey) error {
	rootRole := data.CanonicalRootRole
	targetsRole := data.CanonicalTargetsRole
	snapshotRole := data.CanonicalSnapshotRole
	timestampRole := data.CanonicalTimestampRole

	var oldRootRole *data.RootRole
	newRootRole, ok := newRoot.Signed.Roles[rootRole]
	if !ok {
		return errors.New("new root is missing role entry for root role")
	}

	oldThreshold := 1
	rotation := false
	oldKeys := map[string]data.PublicKey{}
	newKeys := map[string]data.PublicKey{}
	if oldRoot != nil {
		// check for matching root key IDs
		oldRootRole = oldRoot.Signed.Roles[rootRole]
		oldThreshold = oldRootRole.Threshold

		for _, kid := range oldRootRole.KeyIDs {
			k, ok := oldRoot.Signed.Keys[kid]
			if !ok {
				// if the key itself wasn't contained in the root
				// we're skipping it because it could never have
				// been used to validate this root.
				continue
			}
			oldKeys[kid] = data.NewPublicKey(k.Algorithm(), k.Public())
		}

		// super simple check for possible rotation
		rotation = len(oldKeys) != len(newRootRole.KeyIDs)
	}
	// if old and new had the same number of keys, iterate
	// to see if there's a difference.
	for _, kid := range newRootRole.KeyIDs {
		k, ok := newRoot.Signed.Keys[kid]
		if !ok {
			// if the key itself wasn't contained in the root
			// we're skipping it because it could never have
			// been used to validate this root.
			continue
		}
		newKeys[kid] = data.NewPublicKey(k.Algorithm(), k.Public())

		if oldRoot != nil {
			if _, ok := oldKeys[kid]; !ok {
				// if there is any difference in keys, a key rotation may have
				// occurred.
				rotation = true
			}
		}
	}
	newSigned, err := newRoot.ToSigned()
	if err != nil {
		return err
	}
	if rotation {
		err = signed.VerifyRoot(newSigned, oldThreshold, oldKeys)
		if err != nil {
			return fmt.Errorf("rotation detected and new root was not signed with at least %d old keys", oldThreshold)
		}
	}
	err = signed.VerifyRoot(newSigned, newRootRole.Threshold, newKeys)
	if err != nil {
		return err
	}
	root, err := data.RootFromSigned(newSigned)
	if err != nil {
		return err
	}

	var timestampKeyIDs []string

	// at a minimum, check the 4 required roles are present
	for _, r := range []string{rootRole, targetsRole, snapshotRole, timestampRole} {
		role, ok := root.Signed.Roles[r]
		if !ok {
			return fmt.Errorf("missing required %s role from root", r)
		}
		// According to the TUF spec, any role may have more than one signing
		// key and require a threshold signature.  However, notary-server
		// creates the timestamp, and there is only ever one, so a threshold
		// greater than one would just always fail validation
		if (r == timestampRole && role.Threshold != 1) || role.Threshold < 1 {
			return fmt.Errorf("%s role has invalid threshold", r)
		}
		if len(role.KeyIDs) < role.Threshold {
			return fmt.Errorf("%s role has insufficient number of keys", r)
		}

		if r == timestampRole {
			timestampKeyIDs = role.KeyIDs
		}
	}

	// ensure that at least one of the timestamp keys specified in the role
	// actually exists

	for _, keyID := range timestampKeyIDs {
		if timestampKey.ID() == keyID {
			return nil
		}
	}
	return fmt.Errorf("none of the following timestamp keys exist: %s",
		strings.Join(timestampKeyIDs, ", "))
}
Exemple #4
0
// SignRoot signs the root, using all keys from the "root" role (i.e. currently trusted)
// as well as available keys used to sign the previous version, if the public part is
// carried in tr.Root.Keys and the private key is available (i.e. probably previously
// trusted keys, to allow rollover).  If there are any errors, attempt to put root
// back to the way it was (so version won't be incremented, for instance).
func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
	logrus.Debug("signing root...")

	// duplicate root and attempt to modify it rather than the existing root
	rootBytes, err := tr.Root.MarshalJSON()
	if err != nil {
		return nil, err
	}
	tempRoot := data.SignedRoot{}
	if err := json.Unmarshal(rootBytes, &tempRoot); err != nil {
		return nil, err
	}

	currRoot, err := tr.GetBaseRole(data.CanonicalRootRole)
	if err != nil {
		return nil, err
	}

	oldRootRoles := tr.getOldRootRoles()

	var latestSavedRole data.BaseRole
	rolesToSignWith := make([]data.BaseRole, 0, len(oldRootRoles))

	if len(oldRootRoles) > 0 {
		sort.Sort(oldRootRoles)
		for _, vRole := range oldRootRoles {
			rolesToSignWith = append(rolesToSignWith, vRole.BaseRole)
		}
		latest := rolesToSignWith[len(rolesToSignWith)-1]
		latestSavedRole = data.BaseRole{
			Name:      data.CanonicalRootRole,
			Threshold: latest.Threshold,
			Keys:      latest.Keys,
		}
	}

	// If the root role (root keys or root threshold) has changed, save the
	// previous role under the role name "root.<n>", such that the "n" is the
	// latest root.json version for which previous root role was valid.
	// Also, guard against re-saving the previous role if the latest
	// saved role is the same (which should not happen).
	// n   = root.json version of the originalRootRole (previous role)
	// n+1 = root.json version of the currRoot (current role)
	// n-m = root.json version of latestSavedRole (not necessarily n-1, because the
	//       last root rotation could have happened several root.json versions ago
	if !tr.originalRootRole.Equals(currRoot) && !tr.originalRootRole.Equals(latestSavedRole) {
		rolesToSignWith = append(rolesToSignWith, tr.originalRootRole)
		latestSavedRole = tr.originalRootRole

		versionName := oldRootVersionName(tempRoot.Signed.Version)
		tempRoot.Signed.Roles[versionName] = &data.RootRole{
			KeyIDs: latestSavedRole.ListKeyIDs(), Threshold: latestSavedRole.Threshold}
	}

	tempRoot.Signed.Expires = expires
	tempRoot.Signed.Version++
	rolesToSignWith = append(rolesToSignWith, currRoot)

	signed, err := tempRoot.ToSigned()
	if err != nil {
		return nil, err
	}
	signed, err = tr.sign(signed, rolesToSignWith, tr.getOptionalRootKeys(rolesToSignWith))
	if err != nil {
		return nil, err
	}

	tr.Root = &tempRoot
	tr.Root.Signatures = signed.Signatures
	tr.originalRootRole = currRoot
	return signed, nil
}
Exemple #5
0
func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
	now := time.Now()
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)

	pass := func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) {
		return "password", false, nil
	}
	memStore := trustmanager.NewKeyMemoryStore(pass)
	cs := cryptoservice.NewCryptoService(memStore)

	// generate CA cert
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	require.NoError(t, err)
	caTmpl := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			CommonName: "notary testing CA",
		},
		NotBefore:             now.Add(-time.Hour),
		NotAfter:              now.Add(time.Hour),
		KeyUsage:              x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:       true,
		MaxPathLen: 3,
	}
	caPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	require.NoError(t, err)
	_, err = x509.CreateCertificate(
		rand.Reader,
		&caTmpl,
		&caTmpl,
		caPrivKey.Public(),
		caPrivKey,
	)

	// generate intermediate
	intTmpl := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			CommonName: "notary testing intermediate",
		},
		NotBefore:             now.Add(-time.Hour),
		NotAfter:              now.Add(time.Hour),
		KeyUsage:              x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:       true,
		MaxPathLen: 2,
	}
	intPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	require.NoError(t, err)
	intCert, err := x509.CreateCertificate(
		rand.Reader,
		&intTmpl,
		&caTmpl,
		intPrivKey.Public(),
		caPrivKey,
	)
	require.NoError(t, err)

	// generate leaf
	serialNumber, err = rand.Int(rand.Reader, serialNumberLimit)
	require.NoError(t, err)
	leafTmpl := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			CommonName: "docker.io/notary/test",
		},
		NotBefore: now.Add(-time.Hour),
		NotAfter:  now.Add(time.Hour),

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
		BasicConstraintsValid: true,
	}

	leafPubKey, err := cs.Create("root", "docker.io/notary/test", data.ECDSAKey)
	require.NoError(t, err)
	leafPrivKey, _, err := cs.GetPrivateKey(leafPubKey.ID())
	require.NoError(t, err)
	signer := leafPrivKey.CryptoSigner()
	leafCert, err := x509.CreateCertificate(
		rand.Reader,
		&leafTmpl,
		&intTmpl,
		signer.Public(),
		intPrivKey,
	)

	rootBundleWriter := bytes.NewBuffer(nil)
	pem.Encode(
		rootBundleWriter,
		&pem.Block{
			Type:  "CERTIFICATE",
			Bytes: leafCert,
		},
	)
	pem.Encode(
		rootBundleWriter,
		&pem.Block{
			Type:  "CERTIFICATE",
			Bytes: intCert,
		},
	)

	rootBundle := rootBundleWriter.Bytes()

	ecdsax509Key := data.NewECDSAx509PublicKey(rootBundle)

	otherKey, err := cs.Create("targets", "docker.io/notary/test", data.ED25519Key)
	require.NoError(t, err)

	root := data.SignedRoot{
		Signatures: make([]data.Signature, 0),
		Signed: data.Root{
			SignedCommon: data.SignedCommon{
				Type:    "Root",
				Expires: now.Add(time.Hour),
				Version: 1,
			},
			Keys: map[string]data.PublicKey{
				ecdsax509Key.ID(): ecdsax509Key,
				otherKey.ID():     otherKey,
			},
			Roles: map[string]*data.RootRole{
				"root": {
					KeyIDs:    []string{ecdsax509Key.ID()},
					Threshold: 1,
				},
				"targets": {
					KeyIDs:    []string{otherKey.ID()},
					Threshold: 1,
				},
				"snapshot": {
					KeyIDs:    []string{otherKey.ID()},
					Threshold: 1,
				},
				"timestamp": {
					KeyIDs:    []string{otherKey.ID()},
					Threshold: 1,
				},
			},
		},
		Dirty: true,
	}

	signedRoot, err := root.ToSigned()
	require.NoError(t, err)
	err = signed.Sign(cs, signedRoot, []data.PublicKey{ecdsax509Key}, 1, nil)
	require.NoError(t, err)

	typedSignedRoot, err := data.RootFromSigned(signedRoot)
	require.NoError(t, err)

	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
	defer os.RemoveAll(tempBaseDir)
	require.NoError(t, err, "failed to create a temporary directory: %s", err)

	validatedRoot, err := trustpinning.ValidateRoot(
		nil,
		signedRoot,
		"docker.io/notary/test",
		trustpinning.TrustPinConfig{
			Certs: map[string][]string{
				"docker.io/notary/test": {ecdsax509Key.ID()},
			},
			DisableTOFU: true,
		},
	)
	require.NoError(t, err, "failed to validate certID with intermediate")
	generateRootKeyIDs(typedSignedRoot)
	require.Equal(t, typedSignedRoot, validatedRoot)
}