kmsp11/cert_authority.cc (130 lines of code) (raw):

// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "kmsp11/cert_authority.h" #include "absl/strings/escaping.h" #include "absl/time/clock.h" #include "common/status_macros.h" #include "kmsp11/algorithm_details.h" #include "kmsp11/util/crypto_utils.h" #include "kmsp11/util/errors.h" #include "kmsp11/util/string_utils.h" namespace cloud_kms::kmsp11 { namespace { // For OpenSSL sequence functions that take an `int location`, -1 means add to // the end of the sequence. constexpr int kLocationEnd = -1; absl::Status SetRandomSerial(X509* x509) { // Conforming implementations must be able to handle serials up to 20 bytes in // length. https://tools.ietf.org/html/rfc5280#section-4.1.2.2 constexpr size_t kSerialByteLength = 20; std::string serial_bytes = RandBytes(kSerialByteLength); bssl::UniquePtr<BIGNUM> serial( BN_bin2bn(reinterpret_cast<const uint8_t*>(serial_bytes.data()), serial_bytes.size(), nullptr)); if (!BN_to_ASN1_INTEGER(serial.get(), X509_get_serialNumber(x509))) { return NewInternalError( absl::StrCat("error setting serial: ", SslErrorToString()), SOURCE_LOCATION); } return absl::OkStatus(); } // Adds the extension identified by nid with the specified value to the // list of extensions in the X509 ctx. absl::Status AddExtension(X509V3_CTX* ctx, X509* cert, int nid, const char* value) { bssl::UniquePtr<X509_EXTENSION> ext(X509V3_EXT_nconf_nid( /* conf */ nullptr, ctx, nid, const_cast<char*>(value))); if (!ext || X509_add_ext(cert, ext.get(), kLocationEnd) != 1) { return NewInternalError(absl::StrFormat("error adding extension %d: %s", nid, SslErrorToString()), SOURCE_LOCATION); } return absl::OkStatus(); } } // namespace absl::StatusOr<std::unique_ptr<CertAuthority>> CertAuthority::New() { bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); // Generate a new P-256 key for certificate signing. EVP_PKEY* signing_key = nullptr; if (!ctx || EVP_PKEY_keygen_init(ctx.get()) != 1 || EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), NID_X9_62_prime256v1) != 1 || EVP_PKEY_keygen(ctx.get(), &signing_key) != 1 || !signing_key) { return NewInternalError( absl::StrCat("error generating signing key: ", SslErrorToString()), SOURCE_LOCATION); } return std::unique_ptr<CertAuthority>( new CertAuthority(bssl::UniquePtr<EVP_PKEY>(signing_key))); } CertAuthority::CertAuthority(bssl::UniquePtr<EVP_PKEY> signing_key) : signing_key_(std::move(signing_key)), issuer_cn_( // add randomness to the CN to ensure clients don't hardcode absl::StrFormat("cloud-kms-pkcs11-%s", absl::BytesToHexString(RandBytes(4)))) {} absl::StatusOr<bssl::UniquePtr<X509>> CertAuthority::GenerateCert( const kms_v1::CryptoKeyVersion& ckv, EVP_PKEY* public_key) const { bssl::UniquePtr<X509> cert(X509_new()); // Set certificate version = 0x02 (x509 v3) if (X509_set_version(cert.get(), 2) != 1) { return NewInternalError( absl::StrCat("error setting version: ", SslErrorToString()), SOURCE_LOCATION); } RETURN_IF_ERROR(SetRandomSerial(cert.get())); X509_NAME* issuer = X509_get_issuer_name(cert.get()); if (X509_NAME_add_entry_by_NID( issuer, NID_commonName, MBSTRING_ASC, reinterpret_cast<uint8_t*>(const_cast<char*>(issuer_cn_.data())), issuer_cn_.size(), kLocationEnd, 0) != 1) { return NewInternalError( absl::StrCat("error setting issuer: ", SslErrorToString()), SOURCE_LOCATION); } // This will start failing in 2038 on platforms that use 32-bit time_t. // Hopefully we have a better library for dealing with ASN.1 times by then. ASN1_TIME_set(X509_get_notBefore(cert.get()), absl::ToTimeT(absl::Now())); // No well-defined expiration date, per // https://tools.ietf.org/html/rfc5280#section-4.1.2.5 if (ASN1_TIME_set_string(X509_get_notAfter(cert.get()), "99991231235959Z") != 1) { return NewInternalError( absl::StrCat("error setting notAfter: ", SslErrorToString()), SOURCE_LOCATION); } ASSIGN_OR_RETURN(std::string subject_cn, ExtractKeyId(ckv.name())); X509_NAME* subj = X509_get_subject_name(cert.get()); if (X509_NAME_add_entry_by_NID( subj, NID_commonName, MBSTRING_ASC, reinterpret_cast<uint8_t*>(const_cast<char*>(subject_cn.data())), subject_cn.size(), kLocationEnd, 0) != 1) { return NewInternalError( absl::StrCat("error setting subject: ", SslErrorToString()), SOURCE_LOCATION); } if (X509_set_pubkey(cert.get(), public_key) != 1) { return NewInternalError( absl::StrCat("error setting public key: ", SslErrorToString()), SOURCE_LOCATION); } X509V3_CTX ctx; X509V3_set_ctx(&ctx, nullptr, cert.get(), nullptr, nullptr, 0); bssl::UniquePtr<CONF> ext_conf(NCONF_new(nullptr)); X509V3_set_nconf(&ctx, ext_conf.get()); RETURN_IF_ERROR(AddExtension(&ctx, cert.get(), NID_basic_constraints, "critical,CA:false")); RETURN_IF_ERROR(AddExtension( &ctx, cert.get(), NID_certificate_policies, // any policy, from https://tools.ietf.org/html/rfc5280#section-4.2.1.4 "2.5.29.32.0")); RETURN_IF_ERROR( AddExtension(&ctx, cert.get(), NID_subject_key_identifier, "hash")); // We don't add authority key identifier, which is a fairly standard // extension. It's complicated here because we don't actually generate a // self-signed CA cert for the signing key (maybe we should?). ASSIGN_OR_RETURN(AlgorithmDetails algorithm, GetDetails(ckv.algorithm())); switch (algorithm.purpose) { case kms_v1::CryptoKey::ASYMMETRIC_SIGN: RETURN_IF_ERROR(AddExtension(&ctx, cert.get(), NID_key_usage, "critical,digitalSignature")); break; case kms_v1::CryptoKey::ASYMMETRIC_DECRYPT: RETURN_IF_ERROR( AddExtension(&ctx, cert.get(), NID_key_usage, "critical,keyEncipherment,dataEncipherment")); break; default: return NewInternalError( absl::StrFormat("unexpected key purpose: %d", algorithm.purpose), SOURCE_LOCATION); } if (!X509_sign(cert.get(), signing_key_.get(), EVP_sha256())) { return NewInternalError( absl::StrCat("error signing certificate: ", SslErrorToString()), SOURCE_LOCATION); } return std::move(cert); } } // namespace cloud_kms::kmsp11