func TestChromeWarning(t *testing.T) {
	b := newCustomizedBundlerFromFile(t, sha1CA, sha1Intermediate, "")

	s, err := local.NewSignerFromFile(sha1Intermediate, intermediateKey, nil)
	if err != nil {
		t.Fatal(err)
	}

	csrBytes, err := ioutil.ReadFile(leafCSR)
	if err != nil {
		t.Fatal(err)
	}

	signingRequest := signer.SignRequest{Request: string(csrBytes)}

	certBytes, err := s.Sign(signingRequest)
	if err != nil {
		t.Fatal(err)
	}

	// Bundle a leaf cert with default 1 year expiration
	bundle, err := b.BundleFromPEMorDER(certBytes, nil, Ubiquitous, "")
	if err != nil {
		t.Fatal("bundling failed: ", err)
	}

	// should be not ubiquitous due to SHA2 and ECDSA support issues in legacy platforms
	if bundle.Status.Code&errors.BundleNotUbiquitousBit != errors.BundleNotUbiquitousBit {
		t.Fatal("Incorrect bundle status code. Bundle status code:", bundle.Status.Code)
	}

	fullChain := append(bundle.Chain, bundle.Root)
	sha1Msgs := ubiquity.SHA1DeprecationMessages(fullChain)
	// Since the new SHA-1 cert is expired after 2015, it definitely trigger Chrome's deprecation policies.
	if len(sha1Msgs) == 0 {
		t.Fatal("SHA1 Deprecation Message should not be empty")
	}
	// check SHA1 deprecation warnings
	var sha1MsgNotFound bool
	for _, sha1Msg := range sha1Msgs {
		foundMsg := false
		for _, message := range bundle.Status.Messages {
			if message == sha1Msg {
				foundMsg = true
			}
		}
		if !foundMsg {
			sha1MsgNotFound = true
			break
		}
	}
	if sha1MsgNotFound {
		t.Fatalf("Incorrect bundle status messages. Bundle status messages:%v, expected to contain: %v\n", bundle.Status.Messages, sha1Msgs)
	}

}
Example #2
0
// Bundle takes an X509 certificate (already in the
// Certificate structure), a private key as crypto.Signer in one of the appropriate
// formats (i.e. *rsa.PrivateKey or *ecdsa.PrivateKey, or even a opaque key), using them to
// build a certificate bundle.
func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor BundleFlavor) (*Bundle, error) {
	log.Infof("bundling certificate for %+v", certs[0].Subject)
	if len(certs) == 0 {
		return nil, nil
	}

	// Detect reverse ordering of the cert chain.
	if len(certs) > 1 && !partialVerify(certs) {
		rcerts := reverse(certs)
		if partialVerify(rcerts) {
			certs = rcerts
		}
	}

	var ok bool
	cert := certs[0]
	if key != nil {
		switch {
		case cert.PublicKeyAlgorithm == x509.RSA:

			var rsaPublicKey *rsa.PublicKey
			if rsaPublicKey, ok = key.Public().(*rsa.PublicKey); !ok {
				return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
			}
			if cert.PublicKey.(*rsa.PublicKey).N.Cmp(rsaPublicKey.N) != 0 {
				return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
			}
		case cert.PublicKeyAlgorithm == x509.ECDSA:
			var ecdsaPublicKey *ecdsa.PublicKey
			if ecdsaPublicKey, ok = key.Public().(*ecdsa.PublicKey); !ok {
				return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
			}
			if cert.PublicKey.(*ecdsa.PublicKey).X.Cmp(ecdsaPublicKey.X) != 0 {
				return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
			}
		default:
			return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
		}
	} else {
		switch {
		case cert.PublicKeyAlgorithm == x509.RSA:
		case cert.PublicKeyAlgorithm == x509.ECDSA:
		default:
			return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
		}
	}

	bundle := new(Bundle)
	bundle.Cert = cert
	bundle.Key = key
	bundle.Issuer = &cert.Issuer
	bundle.Subject = &cert.Subject

	bundle.buildHostnames()

	if flavor == Force {
		// force bundle checks the certificates
		// forms a verification chain.
		if !partialVerify(certs) {
			return nil,
				errors.Wrap(errors.CertificateError, errors.VerifyFailed,
					goerr.New("Unable to verify the certificate chain"))
		}
		bundle.Chain = certs
	} else {
		// disallow self-signed cert
		if cert.CheckSignatureFrom(cert) == nil {
			return nil, errors.New(errors.CertificateError, errors.SelfSigned)
		}

		// verify and store input intermediates to the intermediate pool.
		// Ignore the returned error here, will treat it in the second call.
		b.fetchIntermediates(certs)

		chains, err := cert.Verify(b.VerifyOptions())
		if err != nil {
			log.Debugf("verification failed: %v", err)
			// If the error was an unknown authority, try to fetch
			// the intermediate specified in the AIA and add it to
			// the intermediates bundle.
			switch err := err.(type) {
			case x509.UnknownAuthorityError:
				// Do nothing -- have the default case return out.
			default:
				return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
			}

			log.Debugf("searching for intermediates via AIA issuer")
			err = b.fetchIntermediates(certs)
			if err != nil {
				log.Debugf("search failed: %v", err)
				return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
			}

			log.Debugf("verifying new chain")
			chains, err = cert.Verify(b.VerifyOptions())
			if err != nil {
				log.Debugf("failed to verify chain: %v", err)
				return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
			}
			log.Debugf("verify ok")
		}
		var matchingChains [][]*x509.Certificate
		switch flavor {
		case Optimal:
			matchingChains = optimalChains(chains)
		case Ubiquitous:
			if len(ubiquity.Platforms) == 0 {
				log.Warning("No metadata, Ubiquitous falls back to Optimal.")
			}
			matchingChains = ubiquitousChains(chains)
		default:
			matchingChains = ubiquitousChains(chains)
		}

		bundle.Chain = matchingChains[0]
	}

	statusCode := int(errors.Success)
	var messages []string
	// Check if bundle is expiring.
	expiringCerts := checkExpiringCerts(bundle.Chain)
	bundle.Expires = helpers.ExpiryTime(bundle.Chain)
	if len(expiringCerts) > 0 {
		statusCode |= errors.BundleExpiringBit
		messages = append(messages, expirationWarning(expiringCerts))
	}
	// Check if bundle contains SHA2 certs.
	if ubiquity.ChainHashUbiquity(bundle.Chain) <= ubiquity.SHA2Ubiquity {
		statusCode |= errors.BundleNotUbiquitousBit
		messages = append(messages, sha2Warning)
	}
	// Check if bundle contains ECDSA signatures.
	if ubiquity.ChainKeyAlgoUbiquity(bundle.Chain) <= ubiquity.ECDSA256Ubiquity {
		statusCode |= errors.BundleNotUbiquitousBit
		messages = append(messages, ecdsaWarning)
	}

	// when forcing a bundle, bundle ubiquity doesn't matter
	// also we don't retrieve the anchoring root of the bundle
	var untrusted []string
	if flavor != Force {
		// Add root store presence info
		root := bundle.Chain[len(bundle.Chain)-1]
		bundle.Root = root
		log.Infof("the anchoring root is %v", root.Subject)
		// Check if there is any platform that doesn't trust the chain.
		// Also, an warning will be generated if ubiquity.Platforms is nil,
		untrusted = ubiquity.UntrustedPlatforms(root)
		untrustedMsg := untrustedPlatformsWarning(untrusted)
		if len(untrustedMsg) > 0 {
			log.Debug("Populate untrusted platform warning.")
			statusCode |= errors.BundleNotUbiquitousBit
			messages = append(messages, untrustedMsg)
		}
	}

	// Check if there is any platform that rejects the chain because of SHA1 deprecation.
	sha1Msgs := ubiquity.SHA1DeprecationMessages(bundle.Chain)
	if len(sha1Msgs) > 0 {
		log.Debug("Populate SHA1 deprecation warning.")
		statusCode |= errors.BundleNotUbiquitousBit
		messages = append(messages, sha1Msgs...)
	}

	bundle.Status = &BundleStatus{ExpiringSKIs: getSKIs(bundle.Chain, expiringCerts), Code: statusCode, Messages: messages, Untrusted: untrusted}

	// attempt to not to include the root certificate for optimization
	if flavor != Force {
		// Include at least one intermediate if the leaf has enabled OCSP and is not CA.
		if bundle.Cert.OCSPServer != nil && !bundle.Cert.IsCA && len(bundle.Chain) <= 2 {
			// No op. Return one intermediate if there is one.
		} else {
			// do not include the root.
			bundle.Chain = bundle.Chain[:len(bundle.Chain)-1]
		}
	}

	bundle.Status.IsRebundled = diff(bundle.Chain, certs)

	log.Debugf("bundle complete")
	return bundle, nil
}