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

Root rotation (previously root cert rotation) #648

Merged
merged 17 commits into from
Apr 14, 2016

Conversation

cyli
Copy link
Contributor

@cyli cyli commented Mar 24, 2016

This is the rebased version + some changes from #267.

To summarize: that PR added library support and CLI support for rotating the public x509 root public key in a root.json, in case it expires. This does not rotate the root private key.

There are a couple additional commits that address some comments from the previous PR:

  1. Certificate rotation #267 (comment) and Certificate rotation #267 (comment): MultiSigning support - currently, notary attempts to support theoretical multisigning, and we would like to preserve this as we would like to implement threshold support for root keys in the near-ish future. However, @mtrmac is correct in that currently, there is no recommended mechanism or any infrastructure in place to handle that. So this PR punts determination of how many signatures are needed up to tuf.Repo's SignRoot, which right now requires that the threshold signatures be met for every required role.
  2. Certificate rotation #267 (comment), Certificate rotation #267 (comment), Certificate rotation #267 (comment): Record of old keys - this maintains the old public keys in the tuf.Repo.Root.Signed.Keys object, but there is otherwise no way in spec to keep track of which of those keys are actually old keys, vs cruft. This PR adds versioned root roles every time root keys are changed to the list of roles in tuf.Repo.Root.Signed.Roles - this technically does not violate the spec, because it doesn't change existing roles, and is not used for validation, only hints w.r.t. which keys to use for signing. It may also be helpful in maintaining a history of root key changes).

This is probably ready for first look. @mtrmac?

@cyli cyli force-pushed the cert-rotation branch 5 times, most recently from d0a5b2c to 54468a5 Compare March 24, 2016 06:29
@cyli cyli changed the title WIP: Cert rotation, redux Cert rotation, redux Mar 24, 2016

// RotateRootCert generates a new certificate to replace oldCert and adds
// a changelist entry to update the root role with this certificate replacement
// shen r.Publish() is called.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: when

@cyli cyli force-pushed the cert-rotation branch 4 times, most recently from b464289 to 833f3d3 Compare March 30, 2016 17:29
for _, cert := range certs {
id, err := trustmanager.FingerprintCert(cert)
if err != nil {
id = fmt.Sprintf("[Error %s]", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since we're using this function for debug strings, can we maybe include Debug or a similar verb to the function name so it isn't accidentally called for other purposes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed it prettyFormatCertIDs

@cyli cyli closed this Mar 30, 2016
@cyli cyli reopened this Mar 30, 2016
@cyli
Copy link
Contributor Author

cyli commented Mar 30, 2016

(sorry, tried closing and reopening to try to kick the docker/dco-signed job)

mtrmac and others added 14 commits April 13, 2016 11:48
This is a refactoring with no behavior change, but it will allow adding
more parameters to the function in the future.

Signed-off-by: Miloslav Trmač <[email protected]>
Tell signed.Sign how many signatures are necessary to sign a role, and
have it fail if it cannot create that many.

For most uses this does not make much of a difference because the
threshold tends to be 1 and signed.Sign was already failing if no key
could be found or if no signature could be created; only >1-threshold
roles now (correctly) fail in additional situations. But the knowledge
of a role’s threshold will be useful in a future commit.

Always use ErrInsufficientSignatures for this failure, whether this is
when loading the keys or actually using them (also fixing
ErrInsufficentSignature documentation to refer to signing and not
verification). ErrNoKeys is no longer returned by signed.Sign.

So, adjust the “snapshot key is not available” logic in
NotaryRepository.Publish accordingly, which also makes it more precise
(actually triggering only when no snapshot key is available).

Now that role's threshold is enforced when signing, update
TestValidateRootInvalidTimestampThreshold to create the second key
necessary to correctly sign the timestamp role.

Signed-off-by: Miloslav Trmač <[email protected]>
Ordinarily we don't want to continue operating on signed data if the
role's threshold of signatures cannot be me and the signature is unsuable.

OTOH we want to keep signing root.json with all older keys if they are
available (to allow migration), but in that case a missing key is not a
fatal error.

So, split the keys passed to signed.Sign into primary and optional,
treating all current uses as primary and enforcing the role's threshold
only on primary keys.  Also update the single existing test which uses
a missing/unusable key to use the optionalKeys parameter.

Note that only the _presence_ of optionalKeys is optional; if an
optional key exists but signing using it fails, the function will fail.

This temporarily breaks the second ErrInsufficientSignatures check
(optional keys count against the role threshold), but that will be fixed
soon.

Signed-off-by: Miloslav Trmač <[email protected]>
Instead of silently ignoring any signing errors, and failing with a
generic ErrInsufficientSignatures, just pass through any signing errors.

As long as the role's threshold is met, some primary keys may be
missing. But if any key does exist (whether primary or optional), failures
using that key are fatal. (This is why signed.Sign needs to know the
role's threshold; making any missing key fatal would break signatures
of roles with 2 valid keys and threshold of 1.)

As a side effect, this improves the “snapshot key is not available”
detection in NotaryRepository.Publish, to trigger only when a key is not
available, not also when it should be available but using it is failing.

Similarly, the ErrInsufficientSignatures error now mentions only the
missing keys, not the successfully used keys now that present but
failing keys are handled by using other error types.

(This removes the second ErrInsufficientSignatures check because it is
no longer necessary, which also fixes the fact that it was broken by
introducing optional keys. The first, now only, ErrInsufficientSignatures
check does correctly distinguish between primary and optional keys.)

Signed-off-by: Miloslav Trmač <[email protected]>
The only thing depending on signed.Sign keeping old signatures was two
tests; all real users were modifying the signed data without clearing
the old signatures, and therefore implicitly relying on signing with
the keys which were used for the old signatures as well.

This broke signing an updated root with a new certificate when the old
certificate was no longer available.

It could have been fixed by keeping signed.Sign as is and adding the
clearing to all users, but noting actually needs the appending
semantics, the appending semantics is surprising, and switching to
replacing signatures is less code.

Signed-off-by: Miloslav Trmač <[email protected]>
Repo.RotateBaseKeys() can now be used to replace the root keypair
(i.e. primarily the expiring certificate), and Repo.SignRoot will make
signatures using the old keypairs if they are available (but not fail if
they are not; only the keypairs listed in the role as trusted are
mandatory).

To do this, we need to keep even the old, possibly currently untrusted,
keypairs around (to allow rollover from clients which trust these old
keys):

The private keys are simply not deleted from the repo's CryptoService;
this means no change for the current setup with a long-term private key
and periodically expiring certificates, but a true rotation of the
private key will eventually require explicit management of the preserved
long-term private keys (if we are keeping several of them around, but
most are either obsolete and non-preferred or possibly even known to be
compromised, we will want to make sure that we always use the
new/preferred private key for new certificate generation).

The public keys are tricker:

1) We need to keep a list of them; the private keys can be looked up
by their IDs, and that allows extracting the public part as well,
but we need a list of the key IDs.  We can't just keep the key IDs
included in the role's list of authorized keys, that would make it
impossible to rotate away from a suspect or known compromsied key.

2) With the X.509 certificate “public keys”, key ID is not actually
sufficient to retrieve the full public key even if we have access to the
private key; we actually need to store the full public key ==
certificate somewhere.  And preferably without having tuf.Repo depend on
a certs.Manager, designed to deal with concepts of trust at a higher
level than TUF cares about.  Actually, to the extent certs.Manager's
purpose is to manage and verify trust, storing old, possibly suspect or
known compromised certificates would be explicitly contrary to its
mission.

So, this patch keeps around full copies of the certificates in the
root.json “keys” map (not the “roles” map of trusted keys). It means
sending to clients a little data which they don't need but it is
otherwise harmless; and keeping the certificates within the
structured and managed tuf.data.Root format could allow us to build nice
UI (e.g. show me all certificates we still carry and keep signing with, let
me drop two of them now that our company has changed a name and does not
want to advertise the history) if we ever needed to something like this

Signed-off-by: Ying Li <[email protected]>
NotaryRepository can now list root certificates, and
generate new versions (as changelists to be applied
on Publish).

This is a pretty mechanical encapsulation of the
root certificate rotation support in Repo.AddBaseKeys
and Repo.RemoveBaseKeys. The only slightly interesting
part is ListRootCert, which requires on-line access
to ensure fresh data, and depends on CertStore doing
some verification for us.

Signed-off-by: Miloslav Trmač <[email protected]>
This is a trivial wrapper around the NotaryRepository functionality. The
UI is simplest possible, a single (notary cert rotate) rotates all
certificates.

This handles the common case (only a single certificate)
perfectly

If there were multiple certificates, rotating all of them
regardless of age does not really hurt; we can easily extend this to
give the user more control (having the user specify a cert ID, for
example) later if necessary.

Signed-off-by: Miloslav Trmač <[email protected]>
This reverts commit aca1cf6, and modifies a test to expect 2 signatures.

Signed-off-by: Ying Li <[email protected]>
…ed as a

versioned root role in the root.json.  That way we can figure out which keys were
previously root keys.

Update tuf.Repo.sign to take a list of required roles (at most two, for root
rotations, because only the immediate root after a rotation absolutely needs
to correctly validate against the previous root role and the new root role)
instead of just a single role.

tuf.Repo.sign now ensures that the number of signatures on the metadata satisfy
role requirements for every required role.  Then it tries to sign with
whatever optional keys it can, ignoring errors and not requiring that any
particular number of signatures were produced with the optional keys.

Signed-off-by: Ying Li <[email protected]>
This reverts commit 684c17867740e77460f2940d3d76023f7a9647ed, and extra cert rotate test changes

Signed-off-by: Ying Li <[email protected]>
err = userRepo.Update(false)
require.NoError(t, err)
logRepoTrustRoot(t, "user refresh 1", userRepo)
require.Equal(t, newRootCertID, rootRoleCertID(t, userRepo))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should test that making some other modification in the root.json (like rotating the targets key), causing it to be signed again still signs with both the old and new keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! Added.

cyli added 3 commits April 13, 2016 22:10
…igned back into

the repo if signing was successful.  This way, we don't mutate the existing root
in a failed attempt to sign it.

Signed-off-by: Ying Li <[email protected]>
@endophage
Copy link
Contributor

LGTM!!! In the immortal words of Queen: "It finally happened"

@riyazdf
Copy link
Contributor

riyazdf commented Apr 14, 2016

LGTM!

@cyli cyli merged commit 2968fa7 into notaryproject:master Apr 14, 2016
@cyli cyli deleted the cert-rotation branch April 14, 2016 18:06
@cyli cyli mentioned this pull request Apr 14, 2016
@cyli cyli mentioned this pull request Apr 22, 2016
@cyli cyli mentioned this pull request May 6, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants