func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool { // reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...}, // in order to get the matching id in the root file leafCertID, err := trustmanager.FingerprintCert(leafCert) if err != nil { return false } rootKeys := trustmanager.CertsToKeys([]*x509.Certificate{leafCert}, map[string][]*x509.Certificate{leafCertID: intCerts}) for keyID := range rootKeys { if utils.StrSliceContains(t.pinnedCertIDs, keyID) { return true } } return false }
/* ValidateRoot receives a new root, validates its correctness and attempts to do root key rotation if needed. First we list the current trusted certificates we have for a particular GUN. If that list is non-empty means that we've already seen this repository before, and have a list of trusted certificates for it. In this case, we use this list of certificates to attempt to validate this root file. If the previous validation suceeds, or in the case where we found no trusted certificates for this particular GUN, we check the integrity of the root by making sure that it is validated by itself. This means that we will attempt to validate the root data with the certificates that are included in the root keys themselves. If this last steps succeeds, we attempt to do root rotation, by ensuring that we only trust the certificates that are present in the new root. This mechanism of operation is essentially Trust On First Use (TOFU): if we have never seen a certificate for a particular CN, we trust it. If later we see a different certificate for that certificate, we return an ErrValidationFailed error. Note that since we only allow trust data to be downloaded over an HTTPS channel we are using the current public PKI to validate the first download of the certificate adding an extra layer of security over the normal (SSH style) trust model. We shall call this: TOFUS. */ func (km *KeyStoreManager) ValidateRoot(root *data.Signed, gun string) error { logrus.Debugf("entered ValidateRoot with dns: %s", gun) signedRoot, err := data.RootFromSigned(root) if err != nil { return err } // Retrieve all the leaf certificates in root for which the CN matches the GUN allValidCerts, err := validRootLeafCerts(signedRoot, gun) if err != nil { logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err) return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"} } // Retrieve all the trusted certificates that match this gun certsForCN, err := km.trustedCertificateStore.GetCertificatesByCN(gun) if err != nil { // If the error that we get back is different than ErrNoCertificatesFound // we couldn't check if there are any certificates with this CN already // trusted. Let's take the conservative approach and return a failed validation if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok { logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err) return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"} } } // If we have certificates that match this specific GUN, let's make sure to // use them first to validate that this new root is valid. if len(certsForCN) != 0 { logrus.Debugf("found %d valid root certificates for %s", len(certsForCN), gun) err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(certsForCN)) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"} } } else { logrus.Debugf("found no currently valid root certificates for %s", gun) } // Validate the integrity of the new root (does it have valid signatures) err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(allValidCerts)) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return &ErrValidationFail{Reason: "failed to validate integrity of roots"} } // Getting here means A) we had trusted certificates and both the // old and new validated this root; or B) we had no trusted certificates but // the new set of certificates has integrity (self-signed) logrus.Debugf("entering root certificate rotation for: %s", gun) // Do root certificate rotation: we trust only the certs present in the new root // First we add all the new certificates (even if they already exist) for _, cert := range allValidCerts { err := km.trustedCertificateStore.AddCert(cert) if err != nil { // If the error is already exists we don't fail the rotation if _, ok := err.(*trustmanager.ErrCertExists); ok { logrus.Debugf("ignoring certificate addition to: %s", gun) continue } logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err) } } // Now we delete old certificates that aren't present in the new root for certID, cert := range certsToRemove(certsForCN, allValidCerts) { logrus.Debugf("removing certificate with certID: %s", certID) err = km.trustedCertificateStore.RemoveCert(cert) if err != nil { logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err) return &ErrRootRotationFail{Reason: "failed to rotate root keys"} } } logrus.Debugf("Root validation succeeded for %s", gun) return nil }
/* ValidateRoot receives a new root, validates its correctness and attempts to do root key rotation if needed. First we check if we have any trusted certificates for a particular GUN in a previous root, if we have one. If the previous root is not nil and we find certificates for this GUN, we've already seen this repository before, and have a list of trusted certificates for it. In this case, we use this list of certificates to attempt to validate this root file. If the previous validation succeeds, we check the integrity of the root by making sure that it is validated by itself. This means that we will attempt to validate the root data with the certificates that are included in the root keys themselves. However, if we do not have any current trusted certificates for this GUN, we check if there are any pinned certificates specified in the trust_pinning section of the notary client config. If this section specifies a Certs section with this GUN, we attempt to validate that the certificates present in the downloaded root file match the pinned ID. If the Certs section is empty for this GUN, we check if the trust_pinning section specifies a CA section specified in the config for this GUN. If so, we check that the specified CA is valid and has signed a certificate included in the downloaded root file. The specified CA can be a prefix for this GUN. If both the Certs and CA configs do not match this GUN, we fall back to the TOFU section in the config: if true, we trust certificates specified in the root for this GUN. If later we see a different certificate for that certificate, we return an ErrValidationFailed error. Note that since we only allow trust data to be downloaded over an HTTPS channel we are using the current public PKI to validate the first download of the certificate adding an extra layer of security over the normal (SSH style) trust model. We shall call this: TOFUS. Validation failure at any step will result in an ErrValidationFailed error. */ func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trustPinning TrustPinConfig) (*data.SignedRoot, error) { logrus.Debugf("entered ValidateRoot with dns: %s", gun) signedRoot, err := data.RootFromSigned(root) if err != nil { return nil, err } rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole) if err != nil { return nil, err } // Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN allLeafCerts, allIntCerts := parseAllCerts(signedRoot) certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true) if err != nil { logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err) return nil, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"} } // If we have a previous root, let's try to use it to validate that this new root is valid. if prevRoot != nil { // Retrieve all the trusted certificates from our previous root // Note that we do not validate expiries here since our originally trusted root might have expired certs allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot) trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false) // Use the certificates we found in the previous root for the GUN to verify its signatures // This could potentially be an empty set, in which case we will fail to verify logrus.Debugf("found %d valid root leaf certificates for %s: %s", len(trustedLeafCerts), gun, prettyFormatCertIDs(trustedLeafCerts)) // Extract the previous root's threshold for signature verification prevRootRoleData, ok := prevRoot.Signed.Roles[data.CanonicalRootRole] if !ok { return nil, &ErrValidationFail{Reason: "could not retrieve previous root role data"} } err = signed.VerifySignatures( root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: prevRootRoleData.Threshold}) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return nil, &ErrRootRotationFail{Reason: "failed to validate data with current trusted certificates"} } } else { logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun) trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun) if err != nil { return nil, &ErrValidationFail{Reason: err.Error()} } validPinnedCerts := map[string]*x509.Certificate{} for id, cert := range certsFromRoot { if ok := trustPinCheckFunc(cert, allIntCerts[id]); !ok { continue } validPinnedCerts[id] = cert } if len(validPinnedCerts) == 0 { return nil, &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"} } certsFromRoot = validPinnedCerts } // Validate the integrity of the new root (does it have valid signatures) // Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS // If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly err = signed.VerifySignatures(root, data.BaseRole{ Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold}) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return nil, &ErrValidationFail{Reason: "failed to validate integrity of roots"} } logrus.Debugf("Root validation succeeded for %s", gun) return signedRoot, nil }
/* ValidateRoot receives a new root, validates its correctness and attempts to do root key rotation if needed. First we list the current trusted certificates we have for a particular GUN. If that list is non-empty means that we've already seen this repository before, and have a list of trusted certificates for it. In this case, we use this list of certificates to attempt to validate this root file. If the previous validation succeeds, we check the integrity of the root by making sure that it is validated by itself. This means that we will attempt to validate the root data with the certificates that are included in the root keys themselves. However, if we do not have any current trusted certificates for this GUN, we check if there are any pinned certificates specified in the trust_pinning section of the notary client config. If this section specifies a Certs section with this GUN, we attempt to validate that the certificates present in the downloaded root file match the pinned ID. If the Certs section is empty for this GUN, we check if the trust_pinning section specifies a CA section specified in the config for this GUN. If so, we check that the specified CA is valid and has signed a certificate included in the downloaded root file. The specified CA can be a prefix for this GUN. If both the Certs and CA configs do not match this GUN, we fall back to the TOFU section in the config: if true, we trust certificates specified in the root for this GUN. If later we see a different certificate for that certificate, we return an ErrValidationFailed error. Note that since we only allow trust data to be downloaded over an HTTPS channel we are using the current public PKI to validate the first download of the certificate adding an extra layer of security over the normal (SSH style) trust model. We shall call this: TOFUS. Validation failure at any step will result in an ErrValidationFailed error. */ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning TrustPinConfig) error { logrus.Debugf("entered ValidateRoot with dns: %s", gun) signedRoot, err := data.RootFromSigned(root) if err != nil { return err } rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole) if err != nil { return err } // Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN allLeafCerts, allIntCerts := parseAllCerts(signedRoot) certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun) if err != nil { logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err) return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"} } // Retrieve all the trusted certificates that match this gun trustedCerts, err := certStore.GetCertificatesByCN(gun) if err != nil { // If the error that we get back is different than ErrNoCertificatesFound // we couldn't check if there are any certificates with this CN already // trusted. Let's take the conservative approach and return a failed validation if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok { logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err) return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"} } } // If we have certificates that match this specific GUN, let's make sure to // use them first to validate that this new root is valid. if len(trustedCerts) != 0 { logrus.Debugf("found %d valid root certificates for %s: %s", len(trustedCerts), gun, prettyFormatCertIDs(trustedCerts)) err = signed.VerifySignatures( root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedCerts, allIntCerts), Threshold: 1}) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"} } } else { logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun) trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun) if err != nil { return &ErrValidationFail{Reason: err.Error()} } validPinnedCerts := []*x509.Certificate{} for _, cert := range certsFromRoot { certID, err := trustmanager.FingerprintCert(cert) if err != nil { continue } if ok := trustPinCheckFunc(cert, allIntCerts[certID]); !ok { continue } validPinnedCerts = append(validPinnedCerts, cert) } if len(validPinnedCerts) == 0 { return &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"} } certsFromRoot = validPinnedCerts } // Validate the integrity of the new root (does it have valid signatures) // Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS // If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly err = signed.VerifySignatures(root, data.BaseRole{ Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold}) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return &ErrValidationFail{Reason: "failed to validate integrity of roots"} } // Getting here means: // A) we had trusted certificates and both the old and new validated this root. // or // B) we had no trusted certificates but the new set of certificates has integrity (self-signed). logrus.Debugf("entering root certificate rotation for: %s", gun) // Do root certificate rotation: we trust only the certs present in the new root // First we add all the new certificates (even if they already exist) for _, cert := range certsFromRoot { err := certStore.AddCert(cert) if err != nil { // If the error is already exists we don't fail the rotation if _, ok := err.(*trustmanager.ErrCertExists); ok { logrus.Debugf("ignoring certificate addition to: %s", gun) continue } logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err) } } // Now we delete old certificates that aren't present in the new root oldCertsToRemove, err := certsToRemove(trustedCerts, certsFromRoot) if err != nil { logrus.Debugf("inconsistency when removing old certificates: %v", err) return err } for certID, cert := range oldCertsToRemove { logrus.Debugf("removing certificate with certID: %s", certID) err = certStore.RemoveCert(cert) if err != nil { logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err) return &ErrRootRotationFail{Reason: "failed to rotate root keys"} } } logrus.Debugf("Root validation succeeded for %s", gun) return nil }