зеркало из https://github.com/microsoft/CCF.git
js: add function to validate a cert chain (#2579)
This commit is contained in:
Родитель
68a483e81c
Коммит
5f24ab38b9
|
@ -42,6 +42,11 @@ export const digest = ccf.digest;
|
|||
*/
|
||||
export const isValidX509CertBundle = ccf.isValidX509CertBundle;
|
||||
|
||||
/**
|
||||
* @inheritDoc CCF.isValidX509CertChain
|
||||
*/
|
||||
export const isValidX509CertChain = ccf.isValidX509CertChain;
|
||||
|
||||
export {
|
||||
WrapAlgoParams,
|
||||
AesKwpParams,
|
||||
|
|
|
@ -245,6 +245,12 @@ export interface CCF {
|
|||
*/
|
||||
isValidX509CertBundle(pem: string): boolean;
|
||||
|
||||
/**
|
||||
* Returns whether a certificate chain is valid given a set of trusted certificates.
|
||||
* The chain and trusted certificates are PEM-encoded bundles of X.509 certificates.
|
||||
*/
|
||||
isValidX509CertChain(chain: string, trusted: string): boolean;
|
||||
|
||||
rpc: {
|
||||
/**
|
||||
* Set whether KV writes should be applied even if the response status is not 2xx.
|
||||
|
|
|
@ -201,6 +201,52 @@ class CCFPolyfill implements CCF {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
isValidX509CertChain(chain: string, trusted: string): boolean {
|
||||
if (!("X509Certificate" in crypto)) {
|
||||
throw new Error(
|
||||
"X509 validation unsupported, Node.js version too old (< 15.6.0)"
|
||||
);
|
||||
}
|
||||
try {
|
||||
const toX509Array = (pem: string) => {
|
||||
const sep = "-----END CERTIFICATE-----";
|
||||
const items = pem.split(sep);
|
||||
if (items.length === 1) {
|
||||
return [];
|
||||
}
|
||||
const pems = items.slice(0, -1).map((p) => p + sep);
|
||||
const arr = pems.map((pem) => new (<any>crypto).X509Certificate(pem));
|
||||
return arr;
|
||||
};
|
||||
const certsChain = toX509Array(chain);
|
||||
const certsTrusted = toX509Array(trusted);
|
||||
if (certsChain.length === 0) {
|
||||
throw new Error("chain cannot be empty");
|
||||
}
|
||||
for (let i = 0; i < certsChain.length - 1; i++) {
|
||||
if (!certsChain[i].checkIssued(certsChain[i + 1])) {
|
||||
throw new Error(`chain[${i}] is not issued by chain[${i + 1}]`);
|
||||
}
|
||||
}
|
||||
for (const certChain of certsChain) {
|
||||
for (const certTrusted of certsTrusted) {
|
||||
if (certChain.fingerprint === certTrusted.fingerprint) {
|
||||
return true;
|
||||
}
|
||||
if (certChain.verify(certTrusted.publicKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
"none of the chain certificates are identical to or issued by a trusted certificate"
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(`certificate chain validation failed: ${e.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(<any>globalThis).ccf = new CCFPolyfill();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as crypto from "crypto";
|
||||
import * as forge from "node-forge";
|
||||
import forge from "node-forge";
|
||||
import { WrapAlgoParams } from "../src/global.js";
|
||||
|
||||
function nodeBufToArrBuf(buf: Buffer): ArrayBuffer {
|
||||
|
@ -82,3 +82,35 @@ export function generateSelfSignedCert(): string {
|
|||
const certPem = forge.pki.certificateToPem(cert);
|
||||
return certPem;
|
||||
}
|
||||
|
||||
export function generateCertChain(len: number): string[] {
|
||||
const keyPairs = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
keyPairs.push(
|
||||
crypto.generateKeyPairSync("rsa", {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: {
|
||||
type: "spki",
|
||||
format: "pem",
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: "pkcs8",
|
||||
format: "pem",
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
const certs = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
const cert = forge.pki.createCertificate();
|
||||
cert.publicKey = forge.pki.publicKeyFromPem(keyPairs[i].publicKey);
|
||||
const signer = i < len - 1 ? keyPairs[i + 1] : keyPairs[i];
|
||||
cert.sign(
|
||||
forge.pki.privateKeyFromPem(signer.privateKey),
|
||||
forge.md.sha256.create()
|
||||
);
|
||||
const certPem = forge.pki.certificateToPem(cert);
|
||||
certs.push(certPem);
|
||||
}
|
||||
return certs;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,11 @@ import {
|
|||
RsaOaepAesKwpParams,
|
||||
RsaOaepParams,
|
||||
} from "../src/global.js";
|
||||
import { unwrapKey, generateSelfSignedCert } from "./crypto.js";
|
||||
import {
|
||||
unwrapKey,
|
||||
generateSelfSignedCert,
|
||||
generateCertChain,
|
||||
} from "./crypto.js";
|
||||
|
||||
beforeEach(function () {
|
||||
// clear KV before each test
|
||||
|
@ -121,6 +125,27 @@ describe("polyfill", function () {
|
|||
assert.isFalse(ccf.isValidX509CertBundle("garbage"));
|
||||
});
|
||||
});
|
||||
describe("isValidX509CertChain", function (this) {
|
||||
const supported = "X509Certificate" in crypto;
|
||||
it("returns true for valid cert chains", function () {
|
||||
if (!supported) {
|
||||
this.skip();
|
||||
}
|
||||
const pems = generateCertChain(3);
|
||||
const chain = [pems[0], pems[1]].join("\n");
|
||||
const trusted = pems[2];
|
||||
assert.isTrue(ccf.isValidX509CertChain(chain, trusted));
|
||||
});
|
||||
it("returns false for invalid cert chains", function () {
|
||||
if (!supported) {
|
||||
this.skip();
|
||||
}
|
||||
const pems = generateCertChain(3);
|
||||
const chain = pems[0];
|
||||
const trusted = pems[2];
|
||||
assert.isFalse(ccf.isValidX509CertChain(chain, trusted));
|
||||
});
|
||||
});
|
||||
describe("kv", function () {
|
||||
it("basic", function () {
|
||||
const foo = ccf.kv["foo"];
|
||||
|
|
|
@ -50,6 +50,11 @@ namespace crypto
|
|||
throw std::invalid_argument(
|
||||
fmt::format("Failed to parse certificate: {}", error_string(rc)));
|
||||
}
|
||||
if (cert.get()->next != nullptr)
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
"PEM string contains more than one certificate");
|
||||
}
|
||||
|
||||
// public_key expects to have unique ownership of the context and so does
|
||||
// `cert`, so we duplicate the key context here.
|
||||
|
@ -108,52 +113,67 @@ namespace crypto
|
|||
class CertificateChain
|
||||
{
|
||||
public:
|
||||
size_t n = 0;
|
||||
mbedtls_x509_crt* raw = NULL;
|
||||
mbedtls_x509_crt raw;
|
||||
|
||||
CertificateChain(const std::vector<const Pem*>& certs)
|
||||
CertificateChain()
|
||||
{
|
||||
if (!certs.empty())
|
||||
mbedtls_x509_crt_init(&raw);
|
||||
}
|
||||
|
||||
void add(const std::vector<const Pem*>& certs)
|
||||
{
|
||||
for (auto& cert : certs)
|
||||
{
|
||||
n = certs.size();
|
||||
raw = new mbedtls_x509_crt[certs.size()];
|
||||
|
||||
for (size_t i = 0; i < certs.size(); i++)
|
||||
int rc = mbedtls_x509_crt_parse(&raw, cert->data(), cert->size());
|
||||
if (rc != 0)
|
||||
{
|
||||
mbedtls_x509_crt_init(&raw[i]);
|
||||
throw std::runtime_error(
|
||||
"Could not parse PEM certificate: " + error_string(rc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < certs.size(); i++)
|
||||
{
|
||||
auto& tc = certs[i];
|
||||
int rc = mbedtls_x509_crt_parse(&raw[i], tc->data(), tc->size());
|
||||
if (rc != 0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Could not parse PEM certificate: " + error_string(rc));
|
||||
}
|
||||
}
|
||||
void add(const uint8_t* der, size_t len)
|
||||
{
|
||||
int rc = mbedtls_x509_crt_parse_der(&raw, der, len);
|
||||
if (rc != 0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Could not parse DER certificate: " + error_string(rc));
|
||||
}
|
||||
}
|
||||
|
||||
~CertificateChain()
|
||||
{
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
mbedtls_x509_crt_free(&raw[i]);
|
||||
}
|
||||
delete[] raw;
|
||||
mbedtls_x509_crt_free(&raw);
|
||||
}
|
||||
};
|
||||
|
||||
bool Verifier_mbedTLS::verify_certificate(
|
||||
const std::vector<const Pem*>& trusted_certs)
|
||||
const std::vector<const Pem*>& trusted_certs,
|
||||
const std::vector<const Pem*>& chain)
|
||||
{
|
||||
CertificateChain chain(trusted_certs);
|
||||
CertificateChain trusted;
|
||||
trusted.add(trusted_certs);
|
||||
|
||||
mbedtls_x509_crt* crt;
|
||||
|
||||
CertificateChain target_and_chain;
|
||||
if (chain.empty())
|
||||
{
|
||||
// Fast-path, avoids extra parse step.
|
||||
crt = cert.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
target_and_chain.add(cert.get()->raw.p, cert.get()->raw.len);
|
||||
target_and_chain.add(chain);
|
||||
crt = &target_and_chain.raw;
|
||||
}
|
||||
|
||||
uint32_t flags;
|
||||
int rc = mbedtls_x509_crt_verify(
|
||||
cert.get(), chain.raw, NULL, NULL, &flags, NULL, NULL);
|
||||
crt, &trusted.raw, NULL, NULL, &flags, NULL, NULL);
|
||||
|
||||
return rc == 0 && flags == 0;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ namespace crypto
|
|||
virtual Pem cert_pem() override;
|
||||
|
||||
virtual bool verify_certificate(
|
||||
const std::vector<const Pem*>& trusted_certs) override;
|
||||
const std::vector<const Pem*>& trusted_certs,
|
||||
const std::vector<const Pem*>& chain = {}) override;
|
||||
|
||||
virtual bool is_self_signed() const override;
|
||||
|
||||
|
|
|
@ -174,6 +174,22 @@ namespace crypto
|
|||
}
|
||||
};
|
||||
|
||||
class Unique_STACK_OF_X509
|
||||
{
|
||||
std::unique_ptr<STACK_OF(X509), void (*)(STACK_OF(X509)*)> p;
|
||||
|
||||
public:
|
||||
Unique_STACK_OF_X509() :
|
||||
p(sk_X509_new_null(), [](auto x) { sk_X509_pop_free(x, X509_free); })
|
||||
{
|
||||
OpenSSL::CHECKNULL(p.get());
|
||||
}
|
||||
operator STACK_OF(X509) * ()
|
||||
{
|
||||
return p.get();
|
||||
}
|
||||
};
|
||||
|
||||
inline std::string error_string(int ec)
|
||||
{
|
||||
return ERR_error_string((unsigned long)ec, NULL);
|
||||
|
|
|
@ -89,7 +89,8 @@ namespace crypto
|
|||
}
|
||||
|
||||
bool Verifier_OpenSSL::verify_certificate(
|
||||
const std::vector<const Pem*>& trusted_certs)
|
||||
const std::vector<const Pem*>& trusted_certs,
|
||||
const std::vector<const Pem*>& chain)
|
||||
{
|
||||
Unique_X509_STORE store;
|
||||
Unique_X509_STORE_CTX store_ctx;
|
||||
|
@ -101,8 +102,37 @@ namespace crypto
|
|||
CHECK1(X509_STORE_add_cert(store, tc));
|
||||
}
|
||||
|
||||
CHECK1(X509_STORE_CTX_init(store_ctx, store, cert, NULL));
|
||||
return X509_verify_cert(store_ctx) == 1;
|
||||
Unique_STACK_OF_X509 chain_stack;
|
||||
for (auto& pem : chain)
|
||||
{
|
||||
Unique_BIO certbio(*pem);
|
||||
Unique_X509 cert(certbio, true);
|
||||
|
||||
CHECK1(sk_X509_push(chain_stack, cert));
|
||||
CHECK1(X509_up_ref(cert));
|
||||
}
|
||||
|
||||
// Allow to use intermediate CAs as trust anchors
|
||||
CHECK1(X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN));
|
||||
|
||||
CHECK1(X509_STORE_CTX_init(store_ctx, store, cert, chain_stack));
|
||||
auto valid = X509_verify_cert(store_ctx) == 1;
|
||||
if (!valid)
|
||||
{
|
||||
auto error = X509_STORE_CTX_get_error(store_ctx);
|
||||
auto msg = X509_verify_cert_error_string(error);
|
||||
LOG_DEBUG_FMT("Failed to verify certificate: {}", msg);
|
||||
LOG_DEBUG_FMT("Target: {}", cert_pem().str());
|
||||
for (auto pem : chain)
|
||||
{
|
||||
LOG_DEBUG_FMT("Chain: {}", pem->str());
|
||||
}
|
||||
for (auto pem : trusted_certs)
|
||||
{
|
||||
LOG_DEBUG_FMT("Trusted: {}", pem->str());
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool Verifier_OpenSSL::is_self_signed() const
|
||||
|
|
|
@ -27,7 +27,8 @@ namespace crypto
|
|||
virtual Pem cert_pem() override;
|
||||
|
||||
virtual bool verify_certificate(
|
||||
const std::vector<const Pem*>& trusted_certs) override;
|
||||
const std::vector<const Pem*>& trusted_certs,
|
||||
const std::vector<const Pem*>& chain = {}) override;
|
||||
|
||||
virtual bool is_self_signed() const override;
|
||||
|
||||
|
|
|
@ -181,10 +181,13 @@ namespace crypto
|
|||
|
||||
/** Verify the certificate (held internally)
|
||||
* @param trusted_certs Vector of trusted certificates
|
||||
* @return true if the
|
||||
* @param chain Vector of ordered untrusted certificates used to
|
||||
* build a chain to trusted certificates
|
||||
* @return true if the verification is successfull
|
||||
*/
|
||||
virtual bool verify_certificate(
|
||||
const std::vector<const Pem*>& trusted_certs) = 0;
|
||||
const std::vector<const Pem*>& trusted_certs,
|
||||
const std::vector<const Pem*>& chain = {}) = 0;
|
||||
|
||||
/** Indicates whether the certificate (held intenally) is self-signed */
|
||||
virtual bool is_self_signed() const = 0;
|
||||
|
|
|
@ -139,6 +139,80 @@ namespace js
|
|||
tls::CA ca(pem);
|
||||
}
|
||||
catch (const std::logic_error& e)
|
||||
{
|
||||
LOG_DEBUG_FMT("isValidX509Bundle: {}", e.what());
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
static std::vector<crypto::Pem> split_x509_cert_bundle(
|
||||
const std::string_view& pem)
|
||||
{
|
||||
std::string separator("-----END CERTIFICATE-----");
|
||||
std::vector<crypto::Pem> pems;
|
||||
auto separator_end = 0;
|
||||
auto next_separator_start = pem.find(separator);
|
||||
while (next_separator_start != std::string_view::npos)
|
||||
{
|
||||
pems.emplace_back(std::string(
|
||||
pem.substr(separator_end, next_separator_start + separator.size())));
|
||||
separator_end = next_separator_start + separator.size();
|
||||
next_separator_start = pem.find(separator, separator_end);
|
||||
}
|
||||
return pems;
|
||||
}
|
||||
|
||||
static JSValue js_is_valid_x509_cert_chain(
|
||||
JSContext* ctx, JSValueConst, int argc, JSValueConst* argv)
|
||||
{
|
||||
// first arg: chain (concatenated PEM certs, first cert = target)
|
||||
// second arg: trusted (concatenated PEM certs)
|
||||
if (argc != 2)
|
||||
return JS_ThrowTypeError(
|
||||
ctx, "Passed %d arguments, but expected 2", argc);
|
||||
|
||||
auto chain_js = argv[0];
|
||||
auto trusted_js = argv[1];
|
||||
|
||||
void* auto_free_ptr = JS_GetContextOpaque(ctx);
|
||||
js::Context& auto_free = *(js::Context*)auto_free_ptr;
|
||||
|
||||
auto chain_cstr = auto_free(JS_ToCString(ctx, chain_js));
|
||||
if (!chain_cstr)
|
||||
{
|
||||
js::js_dump_error(ctx);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
auto trusted_cstr = auto_free(JS_ToCString(ctx, trusted_js));
|
||||
if (!trusted_cstr)
|
||||
{
|
||||
js::js_dump_error(ctx);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto chain_vec = split_x509_cert_bundle(chain_cstr);
|
||||
auto trusted_vec = split_x509_cert_bundle(trusted_cstr);
|
||||
if (chain_vec.empty() || trusted_vec.empty())
|
||||
throw std::logic_error(
|
||||
"chain/trusted arguments must contain at least one certificate");
|
||||
|
||||
auto& target_pem = chain_vec[0];
|
||||
std::vector<const crypto::Pem*> chain_ptr;
|
||||
for (auto it = chain_vec.begin() + 1; it != chain_vec.end(); it++)
|
||||
chain_ptr.push_back(&*it);
|
||||
std::vector<const crypto::Pem*> trusted_ptr;
|
||||
for (auto& pem : trusted_vec)
|
||||
trusted_ptr.push_back(&pem);
|
||||
|
||||
auto verifier = crypto::make_unique_verifier(target_pem);
|
||||
if (!verifier->verify_certificate(trusted_ptr, chain_ptr))
|
||||
throw std::logic_error("certificate chain is invalid");
|
||||
}
|
||||
catch (const std::logic_error& e)
|
||||
{
|
||||
LOG_DEBUG_FMT("isValidX509Chain: {}", e.what());
|
||||
return JS_FALSE;
|
||||
|
|
|
@ -992,6 +992,12 @@ namespace js
|
|||
"isValidX509CertBundle",
|
||||
JS_NewCFunction(
|
||||
ctx, js_is_valid_x509_cert_bundle, "isValidX509CertBundle", 1));
|
||||
JS_SetPropertyStr(
|
||||
ctx,
|
||||
ccf,
|
||||
"isValidX509CertChain",
|
||||
JS_NewCFunction(
|
||||
ctx, js_is_valid_x509_cert_chain, "isValidX509CertChain", 2));
|
||||
JS_SetPropertyStr(
|
||||
ctx, ccf, "pemToId", JS_NewCFunction(ctx, js_pem_to_id, "pemToId", 1));
|
||||
|
||||
|
|
|
@ -79,15 +79,29 @@ def generate_rsa_keypair(key_size: int) -> Tuple[str, str]:
|
|||
return priv_pem, pub_pem
|
||||
|
||||
|
||||
def generate_cert(priv_key_pem: str, cn="dummy") -> str:
|
||||
def generate_cert(
|
||||
priv_key_pem: str, cn="dummy", issuer_priv_key_pem=None, issuer_cn=None, ca=False
|
||||
) -> str:
|
||||
if issuer_priv_key_pem is None:
|
||||
issuer_priv_key_pem = priv_key_pem
|
||||
if issuer_cn is None:
|
||||
issuer_cn = cn
|
||||
priv = load_pem_private_key(priv_key_pem.encode("ascii"), None, default_backend())
|
||||
pub = priv.public_key()
|
||||
subject = issuer = x509.Name(
|
||||
issuer_priv = load_pem_private_key(
|
||||
issuer_priv_key_pem.encode("ascii"), None, default_backend()
|
||||
)
|
||||
subject = x509.Name(
|
||||
[
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, cn),
|
||||
]
|
||||
)
|
||||
cert = (
|
||||
issuer = x509.Name(
|
||||
[
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, issuer_cn),
|
||||
]
|
||||
)
|
||||
builder = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(subject)
|
||||
.issuer_name(issuer)
|
||||
|
@ -95,8 +109,14 @@ def generate_cert(priv_key_pem: str, cn="dummy") -> str:
|
|||
.serial_number(x509.random_serial_number())
|
||||
.not_valid_before(datetime.datetime.utcnow())
|
||||
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=10))
|
||||
.sign(priv, hashes.SHA256(), default_backend())
|
||||
)
|
||||
if ca:
|
||||
builder = builder.add_extension(
|
||||
x509.BasicConstraints(ca=True, path_length=None),
|
||||
critical=True,
|
||||
)
|
||||
|
||||
cert = builder.sign(issuer_priv, hashes.SHA256(), default_backend())
|
||||
|
||||
return cert.public_bytes(Encoding.PEM).decode("ascii")
|
||||
|
||||
|
|
|
@ -357,6 +357,38 @@ def test_npm_app(network, args):
|
|||
r = c.post("/app/isValidX509CertBundle", "garbage")
|
||||
assert not r.body.json(), r.body
|
||||
|
||||
priv_key_pem1, _ = infra.crypto.generate_rsa_keypair(2048)
|
||||
pem1 = infra.crypto.generate_cert(priv_key_pem1, cn="1", ca=True)
|
||||
priv_key_pem2, _ = infra.crypto.generate_rsa_keypair(2048)
|
||||
pem2 = infra.crypto.generate_cert(
|
||||
priv_key_pem2,
|
||||
cn="2",
|
||||
ca=True,
|
||||
issuer_priv_key_pem=priv_key_pem1,
|
||||
issuer_cn="1",
|
||||
)
|
||||
priv_key_pem3, _ = infra.crypto.generate_rsa_keypair(2048)
|
||||
pem3 = infra.crypto.generate_cert(
|
||||
priv_key_pem3, cn="3", issuer_priv_key_pem=priv_key_pem2, issuer_cn="2"
|
||||
)
|
||||
# validates chains with target being trusted directly
|
||||
r = c.post("/app/isValidX509CertChain", {"chain": pem3, "trusted": pem3})
|
||||
assert r.body.json(), r.body
|
||||
# validates chains without intermediates
|
||||
r = c.post("/app/isValidX509CertChain", {"chain": pem2, "trusted": pem1})
|
||||
assert r.body.json(), r.body
|
||||
# validates chains with intermediates
|
||||
r = c.post(
|
||||
"/app/isValidX509CertChain", {"chain": pem3 + "\n" + pem2, "trusted": pem1}
|
||||
)
|
||||
assert r.body.json(), r.body
|
||||
# validates partial chains (pem2 is an intermediate)
|
||||
r = c.post("/app/isValidX509CertChain", {"chain": pem3, "trusted": pem2})
|
||||
assert r.body.json(), r.body
|
||||
# fails to reach trust anchor
|
||||
r = c.post("/app/isValidX509CertChain", {"chain": pem3, "trusted": pem1})
|
||||
assert not r.body.json(), r.body
|
||||
|
||||
r = c.get("/node/quotes/self")
|
||||
primary_quote_info = r.body.json()
|
||||
if not primary_quote_info["raw"]:
|
||||
|
|
|
@ -273,6 +273,48 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/isValidX509CertChain": {
|
||||
"post": {
|
||||
"js_module": "endpoints/crypto.js",
|
||||
"js_function": "isValidX509CertChain",
|
||||
"forwarding_required": "always",
|
||||
"execute_outside_consensus": "never",
|
||||
"authn_policies": ["user_cert"],
|
||||
"mode": "readonly",
|
||||
"openapi": {
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"chain": {
|
||||
"type": "string"
|
||||
},
|
||||
"trusted": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Ok",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/partition": {
|
||||
"post": {
|
||||
"js_module": "endpoints/partition.js",
|
||||
|
|
|
@ -120,6 +120,18 @@ export function isValidX509CertBundle(
|
|||
return { body: ccfcrypto.isValidX509CertBundle(pem) };
|
||||
}
|
||||
|
||||
interface IsValidX509CertChainRequest {
|
||||
chain: string;
|
||||
trusted: string;
|
||||
}
|
||||
|
||||
export function isValidX509CertChain(
|
||||
request: ccfapp.Request<IsValidX509CertChainRequest>
|
||||
): ccfapp.Response<boolean> {
|
||||
const { chain, trusted } = request.body.json();
|
||||
return { body: ccfcrypto.isValidX509CertChain(chain, trusted) };
|
||||
}
|
||||
|
||||
function b64ToBuf(b64: string): ArrayBuffer {
|
||||
return Base64.toUint8Array(b64).buffer;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче