1 /*
2  *  Copyright (c) 2019-present, Facebook, Inc.
3  *  All rights reserved.
4  *
5  *  This source code is licensed under the BSD-style license found in the
6  *  LICENSE file in the root directory of this source tree.
7  */
8 #include <fizz/extensions/delegatedcred/DelegatedCredentialUtils.h>
9 #include <folly/ssl/OpenSSLCertUtils.h>
10 
11 namespace fizz {
12 namespace extensions {
13 
checkExtensions(const folly::ssl::X509UniquePtr & cert)14 void DelegatedCredentialUtils::checkExtensions(
15     const folly::ssl::X509UniquePtr& cert) {
16   if (!hasDelegatedExtension(cert)) {
17     throw FizzException(
18         "cert is missing DelegationUsage extension",
19         AlertDescription::illegal_parameter);
20   }
21 #ifdef LIBRESSL_VERSION_NUMBER
22     throw FizzException(
23         "cert is not suported LibreSSL extension",
24         AlertDescription::illegal_parameter);
25 #else
26   if ((X509_get_extension_flags(cert.get()) & EXFLAG_KUSAGE) != EXFLAG_KUSAGE) {
27     throw FizzException(
28         "cert is missing KeyUsage extension",
29         AlertDescription::illegal_parameter);
30   }
31 
32   auto key_usage = X509_get_key_usage(cert.get());
33   if ((key_usage & KU_DIGITAL_SIGNATURE) != KU_DIGITAL_SIGNATURE) {
34     throw FizzException(
35         "cert lacks digital signature key usage",
36         AlertDescription::illegal_parameter);
37   }
38 #endif
39 }
40 
41 namespace {
42 static constexpr folly::StringPiece kDelegatedOid{"1.3.6.1.4.1.44363.44"};
43 
generateCredentialOid()44 folly::ssl::ASN1ObjUniquePtr generateCredentialOid() {
45   folly::ssl::ASN1ObjUniquePtr oid;
46   oid.reset(OBJ_txt2obj(kDelegatedOid.data(), 1));
47   if (!oid) {
48     throw std::runtime_error("Couldn't create OID for delegated credential");
49   }
50   return oid;
51 }
52 } // namespace
53 
hasDelegatedExtension(const folly::ssl::X509UniquePtr & cert)54 bool DelegatedCredentialUtils::hasDelegatedExtension(
55     const folly::ssl::X509UniquePtr& cert) {
56   static folly::ssl::ASN1ObjUniquePtr credentialOid = generateCredentialOid();
57   // To be valid for a credential, it has to have the delegated credential
58   // extension and the digitalSignature KeyUsage.
59 #ifdef LIBRESSL_VERSION_NUMBER
60   { return false;
61 #else
62   auto credentialIdx = X509_get_ext_by_OBJ(cert.get(), credentialOid.get(), -1);
63   if (credentialIdx == -1) {
64 #endif
65     return false;
66   }
67 
68   return true;
69 }
70 
71 Buf DelegatedCredentialUtils::prepareSignatureBuffer(
72     const DelegatedCredential& cred,
73     Buf certData) {
74   auto toSign = folly::IOBuf::create(0);
75   folly::io::Appender appender(toSign.get(), 10);
76   appender.pushAtMost(certData->data(), certData->length());
77   detail::write(cred.valid_time, appender);
78   detail::write(cred.expected_verify_scheme, appender);
79   detail::writeBuf<detail::bits24>(cred.public_key, appender);
80   detail::write(cred.credential_scheme, appender);
81   return toSign;
82 }
83 
84 DelegatedCredential DelegatedCredentialUtils::generateCredential(
85     std::shared_ptr<SelfCert> cert,
86     const folly::ssl::EvpPkeyUniquePtr& certKey,
87     const folly::ssl::EvpPkeyUniquePtr& credKey,
88     SignatureScheme signScheme,
89     SignatureScheme verifyScheme,
90     std::chrono::seconds validSeconds) {
91   DelegatedCredential cred;
92   if (validSeconds > std::chrono::hours(24 * 7)) {
93     // Can't be valid longer than a week!
94     throw std::runtime_error(
95         "Requested credential with exceedingly large validity");
96   }
97 
98   checkExtensions(cert->getX509());
99 
100   if (X509_check_private_key(cert->getX509().get(), certKey.get()) != 1) {
101     throw std::runtime_error("Cert does not match private key");
102   }
103 
104   std::vector<SignatureScheme> credKeySchemes;
105   switch (CertUtils::getKeyType(credKey)) {
106     case KeyType::RSA:
107       credKeySchemes = CertUtils::getSigSchemes<KeyType::RSA>();
108       break;
109     case KeyType::P256:
110       credKeySchemes = CertUtils::getSigSchemes<KeyType::P256>();
111       break;
112     case KeyType::P384:
113       credKeySchemes = CertUtils::getSigSchemes<KeyType::P384>();
114       break;
115     case KeyType::P521:
116       credKeySchemes = CertUtils::getSigSchemes<KeyType::P521>();
117       break;
118     case KeyType::ED25519:
119       credKeySchemes = CertUtils::getSigSchemes<KeyType::ED25519>();
120       break;
121   }
122 
123   if (std::find(credKeySchemes.begin(), credKeySchemes.end(), verifyScheme) ==
124       credKeySchemes.end()) {
125     throw std::runtime_error(
126         "selected verification scheme not supported by credential key");
127   }
128 
129   auto certSchemes = cert->getSigSchemes();
130   if (std::find(certSchemes.begin(), certSchemes.end(), signScheme) ==
131       certSchemes.end()) {
132     throw std::runtime_error(
133         "credential signature scheme not valid for parent cert");
134   }
135 
136   cred.credential_scheme = signScheme;
137   cred.expected_verify_scheme = verifyScheme;
138 
139   auto notBefore = X509_get0_notBefore(cert->getX509().get());
140   auto notBeforeTime =
141       folly::ssl::OpenSSLCertUtils::asnTimeToTimepoint(notBefore);
142   auto credentialExpiresTime = std::chrono::system_clock::now() + validSeconds;
143   cred.valid_time = std::chrono::duration_cast<std::chrono::seconds>(
144                         credentialExpiresTime - notBeforeTime)
145                         .count();
146 
147   int sz = i2d_PUBKEY(credKey.get(), nullptr);
148   if (sz < 0) {
149     throw std::runtime_error("failed to get delegated pkey size");
150   }
151   unsigned int uSz = static_cast<unsigned int>(sz);
152   cred.public_key = folly::IOBuf::create(uSz);
153   auto ptr = reinterpret_cast<unsigned char*>(cred.public_key->writableData());
154   if (i2d_PUBKEY(credKey.get(), &ptr) < 0) {
155     throw std::runtime_error("failed to convert delegated key to der");
156   }
157   cred.public_key->append(uSz);
158 
159   auto toSign = prepareSignatureBuffer(
160       cred, folly::ssl::OpenSSLCertUtils::derEncode(*cert->getX509()));
161   cred.signature = cert->sign(
162       cred.credential_scheme,
163       CertificateVerifyContext::DelegatedCredential,
164       toSign->coalesce());
165 
166   return cred;
167 }
168 } // namespace extensions
169 } // namespace fizz
170