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