зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1488622 - land NSS 94bcc2706b98 UPGRADE_NSS_RELEASE, r=me
--HG-- extra : rebase_source : 761520ca901dabbf0a908a886732155d0d40d468
This commit is contained in:
Родитель
319c43d823
Коммит
b5cc135a82
|
@ -1 +1 @@
|
|||
a706ba3c4fa9
|
||||
94bcc2706b98
|
||||
|
|
|
@ -99,6 +99,7 @@ while [ $# -gt 0 ]; do
|
|||
--system-nspr) set_nspr_path "/usr/include/nspr/:"; no_local_nspr=1 ;;
|
||||
--enable-libpkix) gyp_params+=(-Ddisable_libpkix=0) ;;
|
||||
--enable-fips) gyp_params+=(-Ddisable_fips=0) ;;
|
||||
--mozpkix-only) gyp_params+=(-Dmozpkix_only=1 -Ddisable_tests=1 -Dsign_libs=0) ;;
|
||||
*) show_help; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
|
|
|
@ -108,8 +108,12 @@
|
|||
'emit_llvm%': 0,
|
||||
'nss_public_dist_dir%': '<(nss_dist_dir)/public',
|
||||
'nss_private_dist_dir%': '<(nss_dist_dir)/private',
|
||||
# This is only needed when building with --mozpkix-only and might not work
|
||||
# on all machines.
|
||||
'nss_include_dir%': '/usr/include/nss',
|
||||
'only_dev_random%': 1,
|
||||
'disable_fips%': 1,
|
||||
'mozpkix_only%': 0,
|
||||
},
|
||||
'target_defaults': {
|
||||
# Settings specific to targets should go here.
|
||||
|
@ -126,6 +130,11 @@
|
|||
'<(nss_dist_dir)/private/<(module)',
|
||||
],
|
||||
'conditions': [
|
||||
[ 'mozpkix_only==1 and OS=="linux"', {
|
||||
'include_dirs': [
|
||||
'<(nss_include_dir)',
|
||||
],
|
||||
}],
|
||||
[ 'disable_fips==1', {
|
||||
'defines': [
|
||||
'NSS_FIPS_DISABLED',
|
||||
|
|
|
@ -10,3 +10,4 @@
|
|||
*/
|
||||
|
||||
#error "Do not include this header file."
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "prerror.h"
|
||||
#include "prio.h"
|
||||
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
class DummyIOLayerMethods {
|
||||
public:
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef scoped_ptrs_h__
|
||||
#define scoped_ptrs_h__
|
||||
#ifndef nss_scoped_ptrs_h__
|
||||
#define nss_scoped_ptrs_h__
|
||||
|
||||
#include <memory>
|
||||
#include "cert.h"
|
||||
|
@ -13,7 +13,6 @@
|
|||
#include "p12.h"
|
||||
#include "pk11pub.h"
|
||||
#include "pkcs11uri.h"
|
||||
#include "sslexp.h"
|
||||
|
||||
struct ScopedDelete {
|
||||
void operator()(CERTCertificate* cert) { CERT_DestroyCertificate(cert); }
|
||||
|
@ -29,6 +28,9 @@ struct ScopedDelete {
|
|||
void operator()(PK11SymKey* key) { PK11_FreeSymKey(key); }
|
||||
void operator()(PRFileDesc* fd) { PR_Close(fd); }
|
||||
void operator()(SECAlgorithmID* id) { SECOID_DestroyAlgorithmID(id, true); }
|
||||
void operator()(SECKEYEncryptedPrivateKeyInfo* e) {
|
||||
SECKEY_DestroyEncryptedPrivateKeyInfo(e, true);
|
||||
}
|
||||
void operator()(SECItem* item) { SECITEM_FreeItem(item, true); }
|
||||
void operator()(SECKEYPublicKey* key) { SECKEY_DestroyPublicKey(key); }
|
||||
void operator()(SECKEYPrivateKey* key) { SECKEY_DestroyPrivateKey(key); }
|
||||
|
@ -39,9 +41,6 @@ struct ScopedDelete {
|
|||
void operator()(PLArenaPool* arena) { PORT_FreeArena(arena, PR_FALSE); }
|
||||
void operator()(PK11Context* context) { PK11_DestroyContext(context, true); }
|
||||
void operator()(PK11GenericObject* obj) { PK11_DestroyGenericObject(obj); }
|
||||
void operator()(SSLResumptionTokenInfo* token) {
|
||||
SSL_DestroyResumptionTokenInfo(token);
|
||||
}
|
||||
void operator()(SEC_PKCS12DecoderContext* dcx) {
|
||||
SEC_PKCS12DecoderFinish(dcx);
|
||||
}
|
||||
|
@ -69,6 +68,7 @@ SCOPED(PK11SlotInfo);
|
|||
SCOPED(PK11SymKey);
|
||||
SCOPED(PRFileDesc);
|
||||
SCOPED(SECAlgorithmID);
|
||||
SCOPED(SECKEYEncryptedPrivateKeyInfo);
|
||||
SCOPED(SECItem);
|
||||
SCOPED(SECKEYPublicKey);
|
||||
SCOPED(SECKEYPrivateKey);
|
||||
|
@ -77,10 +77,9 @@ SCOPED(PK11URI);
|
|||
SCOPED(PLArenaPool);
|
||||
SCOPED(PK11Context);
|
||||
SCOPED(PK11GenericObject);
|
||||
SCOPED(SSLResumptionTokenInfo);
|
||||
SCOPED(SEC_PKCS12DecoderContext);
|
||||
SCOPED(CERTDistNames);
|
||||
|
||||
#undef SCOPED
|
||||
|
||||
#endif // scoped_ptrs_h__
|
||||
#endif // nss_scoped_ptrs_h__
|
|
@ -0,0 +1,35 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef scoped_ptrs_ssl_h__
|
||||
#define scoped_ptrs_ssl_h__
|
||||
|
||||
#include <memory>
|
||||
#include "sslexp.h"
|
||||
|
||||
struct ScopedDeleteSSL {
|
||||
void operator()(SSLResumptionTokenInfo* token) {
|
||||
SSL_DestroyResumptionTokenInfo(token);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct ScopedMaybeDeleteSSL {
|
||||
void operator()(T* ptr) {
|
||||
if (ptr) {
|
||||
ScopedDeleteSSL del;
|
||||
del(ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#define SCOPED(x) typedef std::unique_ptr<x, ScopedMaybeDeleteSSL<x> > Scoped##x
|
||||
|
||||
SCOPED(SSLResumptionTokenInfo);
|
||||
|
||||
#undef SCOPED
|
||||
|
||||
#endif // scoped_ptrs_ssl_h__
|
|
@ -5,13 +5,15 @@
|
|||
'includes': [
|
||||
'coreconf/config.gypi'
|
||||
],
|
||||
'conditions': [
|
||||
[ 'mozpkix_only==0', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'nss_exports',
|
||||
'type': 'none',
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [
|
||||
'<(nss_public_dist_dir)/nss'
|
||||
'<(nss_public_dist_dir)/nss',
|
||||
]
|
||||
},
|
||||
'dependencies': [
|
||||
|
@ -36,7 +38,7 @@
|
|||
'lib/sqlite/exports.gyp:lib_sqlite_exports',
|
||||
'lib/ssl/exports.gyp:lib_ssl_exports',
|
||||
'lib/util/exports.gyp:lib_util_exports',
|
||||
'lib/zlib/exports.gyp:lib_zlib_exports'
|
||||
'lib/zlib/exports.gyp:lib_zlib_exports',
|
||||
],
|
||||
'conditions': [
|
||||
[ 'disable_libpkix==0', {
|
||||
|
@ -73,5 +75,22 @@
|
|||
}],
|
||||
],
|
||||
}
|
||||
],
|
||||
}],
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'nss_mozpkix_exports',
|
||||
'type': 'none',
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [
|
||||
'<(nss_public_dist_dir)/nss'
|
||||
]
|
||||
},
|
||||
'dependencies': [
|
||||
'lib/mozpkix/exports.gyp:lib_mozpkix_exports',
|
||||
'lib/mozpkix/exports.gyp:lib_mozpkix_test_exports',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "ssl.h"
|
||||
|
||||
#include "cpputil.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_server_certs.h"
|
||||
|
||||
const uint8_t kP256ServerCert[] = {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "gtest/gtest.h"
|
||||
|
||||
#include "nss.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "prprf.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "cryptohi.h"
|
||||
#include "secitem.h"
|
||||
#include "secerr.h"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "secutil.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "p12.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "blapi.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "secerr.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
-------------
|
||||
Running Tests
|
||||
-------------
|
||||
|
||||
Because of the rules below, you can run all the unit tests in this directory,
|
||||
and only these tests, with:
|
||||
|
||||
mach gtest "pkix*"
|
||||
|
||||
You can run just the tests for functions defined in filename pkixfoo.cpp with:
|
||||
|
||||
mach gtest "pkixfoo*"
|
||||
|
||||
If you run "mach gtest" then you'll end up running every gtest in Gecko.
|
||||
|
||||
|
||||
|
||||
------------
|
||||
Naming Files
|
||||
------------
|
||||
|
||||
Name files containing tests according to one of the following patterns:
|
||||
|
||||
* <filename>_tests.cpp
|
||||
* <filename>_<Function>_tests.cpp
|
||||
* <filename>_<category>_tests.cpp
|
||||
|
||||
<filename> is the name of the file containing the definitions of the
|
||||
function(s) being tested by every test.
|
||||
<Function> is the name of the function that is being tested by every
|
||||
test.
|
||||
<category> describes the group of related functions that are being
|
||||
tested by every test.
|
||||
|
||||
|
||||
|
||||
------------------------------------------------
|
||||
Always Use a Fixture Class: TEST_F(), not TEST()
|
||||
------------------------------------------------
|
||||
|
||||
Many tests don't technically need a fixture, and so TEST() could technically
|
||||
be used to define the test. However, when you use TEST_F() instead of TEST(),
|
||||
the compiler will not allow you to make any typos in the test case name, but
|
||||
if you use TEST() then the name of the test case is not checked.
|
||||
|
||||
See https://code.google.com/p/googletest/wiki/Primer#Test_Fixtures:_Using_the_Same_Data_Configuration_for_Multiple_Te
|
||||
to learn more about test fixtures.
|
||||
|
||||
---------------
|
||||
Naming Fixtures
|
||||
---------------
|
||||
|
||||
When all tests in a file use the same fixture, use the base name of the file
|
||||
without the "_tests" suffix as the name of the fixture class; e.g. tests in
|
||||
"pkixocsp.cpp" should use a fixture "class pkixocsp" by default.
|
||||
|
||||
Sometimes tests in a file need separate fixtures. In this case, name the
|
||||
fixture class according to the pattern <fixture_base>_<fixture_suffix>, where
|
||||
<fixture_base> is the base name of the file without the "_tests" suffix, and
|
||||
<fixture_suffix> is a descriptive name for the fixture class, e.g.
|
||||
"class pkixocsp_DelegatedResponder".
|
|
@ -0,0 +1,71 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
{
|
||||
'includes': [
|
||||
'../../coreconf/config.gypi',
|
||||
'../common/gtest.gypi',
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'mozpkix_gtest',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'<(DEPTH)/gtests/common/gtests.cc',
|
||||
'pkixbuild_tests.cpp',
|
||||
'pkixcert_extension_tests.cpp',
|
||||
'pkixcert_signature_algorithm_tests.cpp',
|
||||
'pkixcheck_CheckExtendedKeyUsage_tests.cpp',
|
||||
'pkixcheck_CheckIssuer_tests.cpp',
|
||||
'pkixcheck_CheckKeyUsage_tests.cpp',
|
||||
'pkixcheck_CheckSignatureAlgorithm_tests.cpp',
|
||||
'pkixcheck_CheckValidity_tests.cpp',
|
||||
'pkixcheck_ParseValidity_tests.cpp',
|
||||
'pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp',
|
||||
'pkixder_input_tests.cpp',
|
||||
'pkixder_pki_types_tests.cpp',
|
||||
'pkixder_universal_types_tests.cpp',
|
||||
'pkixgtest.cpp',
|
||||
'pkixnames_tests.cpp',
|
||||
'pkixocsp_CreateEncodedOCSPRequest_tests.cpp',
|
||||
'pkixocsp_VerifyEncodedOCSPResponse.cpp',
|
||||
],
|
||||
'dependencies': [
|
||||
'<(DEPTH)/exports.gyp:nss_exports',
|
||||
'<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
|
||||
'<(DEPTH)/lib/util/util.gyp:nssutil',
|
||||
'<(DEPTH)/lib/ssl/ssl.gyp:ssl',
|
||||
'<(DEPTH)/lib/nss/nss.gyp:nss_static',
|
||||
'<(DEPTH)/lib/pk11wrap/pk11wrap.gyp:pk11wrap_static',
|
||||
'<(DEPTH)/lib/cryptohi/cryptohi.gyp:cryptohi',
|
||||
'<(DEPTH)/lib/certhigh/certhigh.gyp:certhi',
|
||||
'<(DEPTH)/lib/certdb/certdb.gyp:certdb',
|
||||
'<(DEPTH)/lib/base/base.gyp:nssb',
|
||||
'<(DEPTH)/lib/dev/dev.gyp:nssdev',
|
||||
'<(DEPTH)/lib/pki/pki.gyp:nsspki',
|
||||
'<(DEPTH)/lib/mozpkix/mozpkix.gyp:mozpkix',
|
||||
'<(DEPTH)/lib/mozpkix/mozpkix.gyp:mozpkix-testlib',
|
||||
],
|
||||
'include_dirs': [
|
||||
'<(DEPTH)/lib/mozpkix/',
|
||||
'<(DEPTH)/lib/mozpkix/lib',
|
||||
'<(DEPTH)/lib/mozpkix/include/',
|
||||
'<(DEPTH)/lib/mozpkix/include/pkix-test/',
|
||||
],
|
||||
'conditions': [
|
||||
[ 'OS=="win"', {
|
||||
'libraries': [
|
||||
'advapi32.lib',
|
||||
],
|
||||
}],
|
||||
],
|
||||
'defines': [
|
||||
'NSS_USE_STATIC_LIBS'
|
||||
],
|
||||
}
|
||||
],
|
||||
'variables': {
|
||||
'module': 'nss',
|
||||
'use_static_libs': 1,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,894 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
// When building with -D_HAS_EXCEPTIONS=0, MSVC's <xtree> header triggers
|
||||
// warning C4702: unreachable code.
|
||||
// https://connect.microsoft.com/VisualStudio/feedback/details/809962
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4702)
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
static ByteString
|
||||
CreateCert(const char* issuerCN, // null means "empty name"
|
||||
const char* subjectCN, // null means "empty name"
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
/*optional modified*/ std::map<ByteString, ByteString>*
|
||||
subjectDERToCertDER = nullptr,
|
||||
/*optional*/ const ByteString* extension = nullptr,
|
||||
/*optional*/ const TestKeyPair* issuerKeyPair = nullptr,
|
||||
/*optional*/ const TestKeyPair* subjectKeyPair = nullptr)
|
||||
{
|
||||
static long serialNumberValue = 0;
|
||||
++serialNumberValue;
|
||||
ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
|
||||
EXPECT_FALSE(ENCODING_FAILED(serialNumber));
|
||||
|
||||
ByteString issuerDER(issuerCN ? CNToDERName(issuerCN) : Name(ByteString()));
|
||||
ByteString subjectDER(subjectCN ? CNToDERName(subjectCN) : Name(ByteString()));
|
||||
|
||||
std::vector<ByteString> extensions;
|
||||
if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
|
||||
ByteString basicConstraints =
|
||||
CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
|
||||
EXPECT_FALSE(ENCODING_FAILED(basicConstraints));
|
||||
extensions.push_back(basicConstraints);
|
||||
}
|
||||
if (extension) {
|
||||
extensions.push_back(*extension);
|
||||
}
|
||||
extensions.push_back(ByteString()); // marks the end of the list
|
||||
|
||||
ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
|
||||
ByteString certDER(CreateEncodedCertificate(
|
||||
v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
|
||||
oneDayBeforeNow, oneDayAfterNow, subjectDER,
|
||||
subjectKeyPair ? *subjectKeyPair : *reusedKey,
|
||||
extensions.data(),
|
||||
issuerKeyPair ? *issuerKeyPair : *reusedKey,
|
||||
sha256WithRSAEncryption()));
|
||||
EXPECT_FALSE(ENCODING_FAILED(certDER));
|
||||
|
||||
if (subjectDERToCertDER) {
|
||||
(*subjectDERToCertDER)[subjectDER] = certDER;
|
||||
}
|
||||
|
||||
return certDER;
|
||||
}
|
||||
|
||||
class TestTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
// The "cert chain tail" is a longish chain of certificates that is used by
|
||||
// all of the tests here. We share this chain across all the tests in order
|
||||
// to speed up the tests (generating keypairs for the certs is very slow).
|
||||
bool SetUpCertChainTail()
|
||||
{
|
||||
static char const* const names[] = {
|
||||
"CA1 (Root)", "CA2", "CA3", "CA4", "CA5", "CA6", "CA7"
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < MOZILLA_PKIX_ARRAY_LENGTH(names); ++i) {
|
||||
const char* issuerName = i == 0 ? names[0] : names[i-1];
|
||||
CreateCACert(issuerName, names[i]);
|
||||
if (i == 0) {
|
||||
rootCACertDER = leafCACertDER;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CreateCACert(const char* issuerName, const char* subjectName)
|
||||
{
|
||||
leafCACertDER = CreateCert(issuerName, subjectName,
|
||||
EndEntityOrCA::MustBeCA, &subjectDERToCertDER);
|
||||
assert(!ENCODING_FAILED(leafCACertDER));
|
||||
}
|
||||
|
||||
ByteString GetLeafCACertDER() const { return leafCACertDER; }
|
||||
|
||||
private:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
|
||||
/*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = InputEqualsByteString(candidateCert, rootCACertDER)
|
||||
? TrustLevel::TrustAnchor
|
||||
: TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
|
||||
override
|
||||
{
|
||||
ByteString subjectDER(InputToByteString(encodedIssuerName));
|
||||
ByteString certDER(subjectDERToCertDER[subjectDER]);
|
||||
Input derCert;
|
||||
Result rv = derCert.Init(certDER.data(), certDER.length());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
bool keepGoing;
|
||||
rv = checker.Check(derCert, nullptr/*additionalNameConstraints*/,
|
||||
keepGoing);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
std::map<ByteString, ByteString> subjectDERToCertDER;
|
||||
ByteString leafCACertDER;
|
||||
ByteString rootCACertDER;
|
||||
};
|
||||
|
||||
class pkixbuild : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
static void SetUpTestCase()
|
||||
{
|
||||
if (!trustDomain.SetUpCertChainTail()) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
static TestTrustDomain trustDomain;
|
||||
};
|
||||
|
||||
/*static*/ TestTrustDomain pkixbuild::trustDomain;
|
||||
|
||||
TEST_F(pkixbuild, MaxAcceptableCertChainLength)
|
||||
{
|
||||
{
|
||||
ByteString leafCACert(trustDomain.GetLeafCACertDER());
|
||||
Input certDER;
|
||||
ASSERT_EQ(Success, certDER.Init(leafCACert.data(), leafCACert.length()));
|
||||
ASSERT_EQ(Success,
|
||||
BuildCertChain(trustDomain, certDER, Now(),
|
||||
EndEntityOrCA::MustBeCA,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
{
|
||||
ByteString certDER(CreateCert("CA7", "Direct End-Entity",
|
||||
EndEntityOrCA::MustBeEndEntity));
|
||||
ASSERT_FALSE(ENCODING_FAILED(certDER));
|
||||
Input certDERInput;
|
||||
ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
|
||||
ASSERT_EQ(Success,
|
||||
BuildCertChain(trustDomain, certDERInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength)
|
||||
{
|
||||
static char const* const caCertName = "CA Too Far";
|
||||
|
||||
trustDomain.CreateCACert("CA7", caCertName);
|
||||
|
||||
{
|
||||
ByteString certDER(trustDomain.GetLeafCACertDER());
|
||||
Input certDERInput;
|
||||
ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
|
||||
ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
|
||||
BuildCertChain(trustDomain, certDERInput, Now(),
|
||||
EndEntityOrCA::MustBeCA,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
{
|
||||
ByteString certDER(CreateCert(caCertName, "End-Entity Too Far",
|
||||
EndEntityOrCA::MustBeEndEntity));
|
||||
ASSERT_FALSE(ENCODING_FAILED(certDER));
|
||||
Input certDERInput;
|
||||
ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
|
||||
ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
|
||||
BuildCertChain(trustDomain, certDERInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
}
|
||||
|
||||
// A TrustDomain that checks certificates against a given root certificate.
|
||||
// It is initialized with the DER encoding of a root certificate that
|
||||
// is treated as a trust anchor and is assumed to have issued all certificates
|
||||
// (i.e. FindIssuer always attempts to build the next step in the chain with
|
||||
// it).
|
||||
class SingleRootTrustDomain : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit SingleRootTrustDomain(ByteString aRootDER)
|
||||
: rootDER(aRootDER)
|
||||
{
|
||||
}
|
||||
|
||||
// The CertPolicyId argument is unused because we don't care about EV.
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
|
||||
/*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
Input rootCert;
|
||||
Result rv = rootCert.Init(rootDER.data(), rootDER.length());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (InputsAreEqual(candidateCert, rootCert)) {
|
||||
trustLevel = TrustLevel::TrustAnchor;
|
||||
} else {
|
||||
trustLevel = TrustLevel::InheritsTrust;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker& checker, Time) override
|
||||
{
|
||||
// keepGoing is an out parameter from IssuerChecker.Check. It would tell us
|
||||
// whether or not to continue attempting other potential issuers. We only
|
||||
// know of one potential issuer, however, so we ignore it.
|
||||
bool keepGoing;
|
||||
Input rootCert;
|
||||
Result rv = rootCert.Init(rootDER.data(), rootDER.length());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return checker.Check(rootCert, nullptr, keepGoing);
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
private:
|
||||
ByteString rootDER;
|
||||
};
|
||||
|
||||
// A TrustDomain that explicitly fails if CheckRevocation is called.
|
||||
class ExpiredCertTrustDomain final : public SingleRootTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit ExpiredCertTrustDomain(ByteString aRootDER)
|
||||
: SingleRootTrustDomain(aRootDER)
|
||||
{
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckRevocation should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert)
|
||||
{
|
||||
const char* rootCN = "Root CA";
|
||||
ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA,
|
||||
nullptr));
|
||||
EXPECT_FALSE(ENCODING_FAILED(rootDER));
|
||||
ExpiredCertTrustDomain expiredCertTrustDomain(rootDER);
|
||||
|
||||
ByteString serialNumber(CreateEncodedSerialNumber(100));
|
||||
EXPECT_FALSE(ENCODING_FAILED(serialNumber));
|
||||
ByteString issuerDER(CNToDERName(rootCN));
|
||||
ByteString subjectDER(CNToDERName("Expired End-Entity Cert"));
|
||||
ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
|
||||
ByteString certDER(CreateEncodedCertificate(
|
||||
v3, sha256WithRSAEncryption(),
|
||||
serialNumber, issuerDER,
|
||||
twoDaysBeforeNow,
|
||||
oneDayBeforeNow,
|
||||
subjectDER, *reusedKey, nullptr, *reusedKey,
|
||||
sha256WithRSAEncryption()));
|
||||
EXPECT_FALSE(ENCODING_FAILED(certDER));
|
||||
|
||||
Input cert;
|
||||
ASSERT_EQ(Success, cert.Init(certDER.data(), certDER.length()));
|
||||
ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE,
|
||||
BuildCertChain(expiredCertTrustDomain, cert, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
class DSSTrustDomain final : public EverythingFailsByDefaultTrustDomain
|
||||
{
|
||||
public:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
|
||||
Input, /*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = TrustLevel::TrustAnchor;
|
||||
return Success;
|
||||
}
|
||||
};
|
||||
|
||||
class pkixbuild_DSS : public ::testing::Test { };
|
||||
|
||||
TEST_F(pkixbuild_DSS, DSSEndEntityKeyNotAccepted)
|
||||
{
|
||||
DSSTrustDomain trustDomain;
|
||||
|
||||
ByteString serialNumber(CreateEncodedSerialNumber(1));
|
||||
ASSERT_FALSE(ENCODING_FAILED(serialNumber));
|
||||
|
||||
ByteString subjectDER(CNToDERName("DSS"));
|
||||
ASSERT_FALSE(ENCODING_FAILED(subjectDER));
|
||||
ScopedTestKeyPair subjectKey(GenerateDSSKeyPair());
|
||||
ASSERT_TRUE(subjectKey.get());
|
||||
|
||||
ByteString issuerDER(CNToDERName("RSA"));
|
||||
ASSERT_FALSE(ENCODING_FAILED(issuerDER));
|
||||
ScopedTestKeyPair issuerKey(CloneReusedKeyPair());
|
||||
ASSERT_TRUE(issuerKey.get());
|
||||
|
||||
ByteString cert(CreateEncodedCertificate(v3, sha256WithRSAEncryption(),
|
||||
serialNumber, issuerDER,
|
||||
oneDayBeforeNow, oneDayAfterNow,
|
||||
subjectDER, *subjectKey, nullptr,
|
||||
*issuerKey, sha256WithRSAEncryption()));
|
||||
ASSERT_FALSE(ENCODING_FAILED(cert));
|
||||
Input certDER;
|
||||
ASSERT_EQ(Success, certDER.Init(cert.data(), cert.length()));
|
||||
|
||||
ASSERT_EQ(Result::ERROR_UNSUPPORTED_KEYALG,
|
||||
BuildCertChain(trustDomain, certDER, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
class IssuerNameCheckTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
IssuerNameCheckTrustDomain(const ByteString& aIssuer, bool aExpectedKeepGoing)
|
||||
: issuer(aIssuer)
|
||||
, expectedKeepGoing(aExpectedKeepGoing)
|
||||
{
|
||||
}
|
||||
|
||||
Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, Input,
|
||||
/*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = endEntityOrCA == EndEntityOrCA::MustBeCA
|
||||
? TrustLevel::TrustAnchor
|
||||
: TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker& checker, Time) override
|
||||
{
|
||||
Input issuerInput;
|
||||
EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length()));
|
||||
bool keepGoing;
|
||||
EXPECT_EQ(Success,
|
||||
checker.Check(issuerInput, nullptr /*additionalNameConstraints*/,
|
||||
keepGoing));
|
||||
EXPECT_EQ(expectedKeepGoing, keepGoing);
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
private:
|
||||
const ByteString issuer;
|
||||
const bool expectedKeepGoing;
|
||||
};
|
||||
|
||||
struct IssuerNameCheckParams
|
||||
{
|
||||
const char* subjectIssuerCN; // null means "empty name"
|
||||
const char* issuerSubjectCN; // null means "empty name"
|
||||
bool matches;
|
||||
Result expectedError;
|
||||
};
|
||||
|
||||
static const IssuerNameCheckParams ISSUER_NAME_CHECK_PARAMS[] =
|
||||
{
|
||||
{ "foo", "foo", true, Success },
|
||||
{ "foo", "bar", false, Result::ERROR_UNKNOWN_ISSUER },
|
||||
{ "f", "foo", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix
|
||||
{ "foo", "f", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix
|
||||
{ "foo", "Foo", false, Result::ERROR_UNKNOWN_ISSUER }, // case sensitive
|
||||
{ "", "", true, Success },
|
||||
{ nullptr, nullptr, false, Result::ERROR_EMPTY_ISSUER_NAME }, // empty issuer
|
||||
|
||||
// check that certificate-related errors are deferred and superseded by
|
||||
// ERROR_UNKNOWN_ISSUER when a chain can't be built due to name mismatches
|
||||
{ "foo", nullptr, false, Result::ERROR_UNKNOWN_ISSUER },
|
||||
{ nullptr, "foo", false, Result::ERROR_UNKNOWN_ISSUER }
|
||||
};
|
||||
|
||||
class pkixbuild_IssuerNameCheck
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<IssuerNameCheckParams>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(pkixbuild_IssuerNameCheck, MatchingName)
|
||||
{
|
||||
const IssuerNameCheckParams& params(GetParam());
|
||||
|
||||
ByteString issuerCertDER(CreateCert(params.issuerSubjectCN,
|
||||
params.issuerSubjectCN,
|
||||
EndEntityOrCA::MustBeCA, nullptr));
|
||||
ASSERT_FALSE(ENCODING_FAILED(issuerCertDER));
|
||||
|
||||
ByteString subjectCertDER(CreateCert(params.subjectIssuerCN, "end-entity",
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr));
|
||||
ASSERT_FALSE(ENCODING_FAILED(subjectCertDER));
|
||||
|
||||
Input subjectCertDERInput;
|
||||
ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(),
|
||||
subjectCertDER.length()));
|
||||
|
||||
IssuerNameCheckTrustDomain trustDomain(issuerCertDER, !params.matches);
|
||||
ASSERT_EQ(params.expectedError,
|
||||
BuildCertChain(trustDomain, subjectCertDERInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(pkixbuild_IssuerNameCheck, pkixbuild_IssuerNameCheck,
|
||||
testing::ValuesIn(ISSUER_NAME_CHECK_PARAMS));
|
||||
|
||||
|
||||
// Records the embedded SCT list extension for later examination.
|
||||
class EmbeddedSCTListTestTrustDomain final : public SingleRootTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit EmbeddedSCTListTestTrustDomain(ByteString aRootDER)
|
||||
: SingleRootTrustDomain(aRootDER)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
|
||||
Input extensionData) override
|
||||
{
|
||||
if (extension == AuxiliaryExtension::EmbeddedSCTList) {
|
||||
signedCertificateTimestamps = InputToByteString(extensionData);
|
||||
} else {
|
||||
ADD_FAILURE();
|
||||
}
|
||||
}
|
||||
|
||||
ByteString signedCertificateTimestamps;
|
||||
};
|
||||
|
||||
TEST_F(pkixbuild, CertificateTransparencyExtension)
|
||||
{
|
||||
// python security/pkix/tools/DottedOIDToCode.py --tlv
|
||||
// id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
|
||||
static const uint8_t tlv_id_embeddedSctList[] = {
|
||||
0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
|
||||
};
|
||||
static const uint8_t dummySctList[] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05
|
||||
};
|
||||
|
||||
ByteString ctExtension = TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_embeddedSctList) +
|
||||
Boolean(false) +
|
||||
TLV(der::OCTET_STRING,
|
||||
// SignedCertificateTimestampList structure is encoded as an OCTET STRING
|
||||
// within the X.509v3 extension (see RFC 6962 section 3.3).
|
||||
// pkix decodes it internally and returns the actual structure.
|
||||
TLV(der::OCTET_STRING, BytesToByteString(dummySctList))));
|
||||
|
||||
const char* rootCN = "Root CA";
|
||||
ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA));
|
||||
ASSERT_FALSE(ENCODING_FAILED(rootDER));
|
||||
|
||||
ByteString certDER(CreateCert(rootCN, "Cert with SCT list",
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr, /*subjectDERToCertDER*/
|
||||
&ctExtension));
|
||||
ASSERT_FALSE(ENCODING_FAILED(certDER));
|
||||
|
||||
Input certInput;
|
||||
ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
|
||||
|
||||
EmbeddedSCTListTestTrustDomain extTrustDomain(rootDER);
|
||||
ASSERT_EQ(Success,
|
||||
BuildCertChain(extTrustDomain, certInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr /*stapledOCSPResponse*/));
|
||||
ASSERT_EQ(BytesToByteString(dummySctList),
|
||||
extTrustDomain.signedCertificateTimestamps);
|
||||
}
|
||||
|
||||
// This TrustDomain implements a hierarchy like so:
|
||||
//
|
||||
// A B
|
||||
// | |
|
||||
// C D
|
||||
// \ /
|
||||
// E
|
||||
//
|
||||
// where A is a trust anchor, B is not a trust anchor and has no known issuer, C
|
||||
// and D are intermediates with the same subject and subject public key, and E
|
||||
// is an end-entity (in practice, the end-entity will be generated by the test
|
||||
// functions using this trust domain).
|
||||
class MultiplePathTrustDomain: public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
void SetUpCerts()
|
||||
{
|
||||
ASSERT_FALSE(ENCODING_FAILED(CreateCert("UntrustedRoot", "UntrustedRoot",
|
||||
EndEntityOrCA::MustBeCA,
|
||||
&subjectDERToCertDER)));
|
||||
// The subject DER -> cert DER mapping would be overwritten for subject
|
||||
// "Intermediate" when we create the second "Intermediate" certificate, so
|
||||
// we keep a copy of this "Intermediate".
|
||||
intermediateSignedByUntrustedRootCertDER =
|
||||
CreateCert("UntrustedRoot", "Intermediate", EndEntityOrCA::MustBeCA);
|
||||
ASSERT_FALSE(ENCODING_FAILED(intermediateSignedByUntrustedRootCertDER));
|
||||
rootCACertDER = CreateCert("TrustedRoot", "TrustedRoot",
|
||||
EndEntityOrCA::MustBeCA, &subjectDERToCertDER);
|
||||
ASSERT_FALSE(ENCODING_FAILED(rootCACertDER));
|
||||
ASSERT_FALSE(ENCODING_FAILED(CreateCert("TrustedRoot", "Intermediate",
|
||||
EndEntityOrCA::MustBeCA,
|
||||
&subjectDERToCertDER)));
|
||||
}
|
||||
|
||||
private:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
|
||||
/*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = InputEqualsByteString(candidateCert, rootCACertDER)
|
||||
? TrustLevel::TrustAnchor
|
||||
: TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckCert(ByteString& certDER, IssuerChecker& checker, bool& keepGoing)
|
||||
{
|
||||
Input derCert;
|
||||
Result rv = derCert.Init(certDER.data(), certDER.length());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return checker.Check(derCert, nullptr/*additionalNameConstraints*/,
|
||||
keepGoing);
|
||||
}
|
||||
|
||||
Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
|
||||
override
|
||||
{
|
||||
ByteString subjectDER(InputToByteString(encodedIssuerName));
|
||||
ByteString certDER(subjectDERToCertDER[subjectDER]);
|
||||
assert(!ENCODING_FAILED(certDER));
|
||||
bool keepGoing;
|
||||
Result rv = CheckCert(certDER, checker, keepGoing);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
// Also try the other intermediate.
|
||||
if (keepGoing) {
|
||||
rv = CheckCert(intermediateSignedByUntrustedRootCertDER, checker,
|
||||
keepGoing);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*,
|
||||
/*optional*/ const Input*) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
std::map<ByteString, ByteString> subjectDERToCertDER;
|
||||
ByteString rootCACertDER;
|
||||
ByteString intermediateSignedByUntrustedRootCertDER;
|
||||
};
|
||||
|
||||
TEST_F(pkixbuild, BadEmbeddedSCTWithMultiplePaths)
|
||||
{
|
||||
MultiplePathTrustDomain localTrustDomain;
|
||||
localTrustDomain.SetUpCerts();
|
||||
|
||||
// python security/pkix/tools/DottedOIDToCode.py --tlv
|
||||
// id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
|
||||
static const uint8_t tlv_id_embeddedSctList[] = {
|
||||
0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
|
||||
};
|
||||
static const uint8_t dummySctList[] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05
|
||||
};
|
||||
ByteString ctExtension = TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_embeddedSctList) +
|
||||
Boolean(false) +
|
||||
// The contents of the OCTET STRING are supposed to consist of an OCTET
|
||||
// STRING of useful data. We're testing what happens if it isn't, so shove
|
||||
// some bogus (non-OCTET STRING) data in there.
|
||||
TLV(der::OCTET_STRING, BytesToByteString(dummySctList)));
|
||||
ByteString certDER(CreateCert("Intermediate", "Cert with bogus SCT list",
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr, /*subjectDERToCertDER*/
|
||||
&ctExtension));
|
||||
ASSERT_FALSE(ENCODING_FAILED(certDER));
|
||||
Input certDERInput;
|
||||
ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
BuildCertChain(localTrustDomain, certDERInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
// Same as a MultiplePathTrustDomain, but the end-entity is revoked.
|
||||
class RevokedEndEntityTrustDomain final : public MultiplePathTrustDomain
|
||||
{
|
||||
public:
|
||||
Result CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID&, Time,
|
||||
Duration, /*optional*/ const Input*,
|
||||
/*optional*/ const Input*) override
|
||||
{
|
||||
if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
|
||||
return Result::ERROR_REVOKED_CERTIFICATE;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(pkixbuild, RevokedEndEntityWithMultiplePaths)
|
||||
{
|
||||
RevokedEndEntityTrustDomain localTrustDomain;
|
||||
localTrustDomain.SetUpCerts();
|
||||
ByteString certDER(CreateCert("Intermediate", "RevokedEndEntity",
|
||||
EndEntityOrCA::MustBeEndEntity));
|
||||
ASSERT_FALSE(ENCODING_FAILED(certDER));
|
||||
Input certDERInput;
|
||||
ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
|
||||
ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE,
|
||||
BuildCertChain(localTrustDomain, certDERInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
// This represents a collection of different certificates that all have the same
|
||||
// subject and issuer distinguished name.
|
||||
class SelfIssuedCertificatesTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
void SetUpCerts(size_t totalCerts)
|
||||
{
|
||||
ASSERT_TRUE(totalCerts > 0);
|
||||
// First we generate a trust anchor.
|
||||
ScopedTestKeyPair rootKeyPair(GenerateKeyPair());
|
||||
rootCACertDER = CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
|
||||
nullptr, rootKeyPair.get(), rootKeyPair.get());
|
||||
ASSERT_FALSE(ENCODING_FAILED(rootCACertDER));
|
||||
certs.push_back(rootCACertDER);
|
||||
ScopedTestKeyPair issuerKeyPair(rootKeyPair.release());
|
||||
size_t subCAsGenerated;
|
||||
// Then we generate 6 sub-CAs (given that we were requested to generate at
|
||||
// least that many).
|
||||
for (subCAsGenerated = 0;
|
||||
subCAsGenerated < totalCerts - 1 && subCAsGenerated < 6;
|
||||
subCAsGenerated++) {
|
||||
// Each certificate has to have a unique SPKI (mozilla::pkix does loop
|
||||
// detection and stops searching if it encounters two certificates in a
|
||||
// path with the same subject and SPKI).
|
||||
ScopedTestKeyPair keyPair(GenerateKeyPair());
|
||||
ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
|
||||
nullptr, issuerKeyPair.get(), keyPair.get()));
|
||||
ASSERT_FALSE(ENCODING_FAILED(cert));
|
||||
certs.push_back(cert);
|
||||
issuerKeyPair.reset(keyPair.release());
|
||||
}
|
||||
// We set firstIssuerKey here because we can't end up with a path that has
|
||||
// more than 7 CAs in it (because mozilla::pkix limits the path length).
|
||||
firstIssuerKey.reset(issuerKeyPair.release());
|
||||
// For any more sub CAs we generate, it doesn't matter what their keys are
|
||||
// as long as they're different.
|
||||
for (; subCAsGenerated < totalCerts - 1; subCAsGenerated++) {
|
||||
ScopedTestKeyPair keyPair(GenerateKeyPair());
|
||||
ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
|
||||
nullptr, keyPair.get(), keyPair.get()));
|
||||
ASSERT_FALSE(ENCODING_FAILED(cert));
|
||||
certs.insert(certs.begin(), cert);
|
||||
}
|
||||
}
|
||||
|
||||
const TestKeyPair* GetFirstIssuerKey()
|
||||
{
|
||||
return firstIssuerKey.get();
|
||||
}
|
||||
|
||||
private:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
|
||||
/*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = InputEqualsByteString(candidateCert, rootCACertDER)
|
||||
? TrustLevel::TrustAnchor
|
||||
: TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker& checker, Time) override
|
||||
{
|
||||
bool keepGoing;
|
||||
for (auto& cert: certs) {
|
||||
Input certInput;
|
||||
Result rv = certInput.Init(cert.data(), cert.length());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = checker.Check(certInput, nullptr, keepGoing);
|
||||
if (rv != Success || !keepGoing) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
std::vector<ByteString> certs;
|
||||
ByteString rootCACertDER;
|
||||
ScopedTestKeyPair firstIssuerKey;
|
||||
};
|
||||
|
||||
TEST_F(pkixbuild, AvoidUnboundedPathSearchingFailure)
|
||||
{
|
||||
SelfIssuedCertificatesTrustDomain localTrustDomain;
|
||||
// This creates a few hundred million potential paths of length 8 (end entity
|
||||
// + 6 sub-CAs + root). It would be prohibitively expensive to enumerate all
|
||||
// of these, so we give mozilla::pkix a budget that is spent when searching
|
||||
// paths. If the budget is exhausted, it simply returns an unknown issuer
|
||||
// error. In the future it might be nice to return a specific error that would
|
||||
// give the front-end a hint that maybe it shouldn't have so many certificates
|
||||
// that all have the same subject and issuer DN but different SPKIs.
|
||||
localTrustDomain.SetUpCerts(18);
|
||||
ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr, nullptr,
|
||||
localTrustDomain.GetFirstIssuerKey()));
|
||||
ASSERT_FALSE(ENCODING_FAILED(certDER));
|
||||
Input certDERInput;
|
||||
ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
|
||||
ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
|
||||
BuildCertChain(localTrustDomain, certDERInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
TEST_F(pkixbuild, AvoidUnboundedPathSearchingSuccess)
|
||||
{
|
||||
SelfIssuedCertificatesTrustDomain localTrustDomain;
|
||||
// This creates a few hundred thousand possible potential paths of length 8
|
||||
// (end entity + 6 sub-CAs + root). This will nearly exhaust mozilla::pkix's
|
||||
// search budget, so this should succeed.
|
||||
localTrustDomain.SetUpCerts(10);
|
||||
ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr, nullptr,
|
||||
localTrustDomain.GetFirstIssuerKey()));
|
||||
ASSERT_FALSE(ENCODING_FAILED(certDER));
|
||||
Input certDERInput;
|
||||
ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
|
||||
ASSERT_EQ(Success,
|
||||
BuildCertChain(localTrustDomain, certDERInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
#include "mozpkix/test/pkixtestutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
// Creates a self-signed certificate with the given extension.
|
||||
static ByteString
|
||||
CreateCertWithExtensions(const char* subjectCN,
|
||||
const ByteString* extensions)
|
||||
{
|
||||
static long serialNumberValue = 0;
|
||||
++serialNumberValue;
|
||||
ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
|
||||
EXPECT_FALSE(ENCODING_FAILED(serialNumber));
|
||||
ByteString issuerDER(CNToDERName(subjectCN));
|
||||
EXPECT_FALSE(ENCODING_FAILED(issuerDER));
|
||||
ByteString subjectDER(CNToDERName(subjectCN));
|
||||
EXPECT_FALSE(ENCODING_FAILED(subjectDER));
|
||||
ScopedTestKeyPair subjectKey(CloneReusedKeyPair());
|
||||
return CreateEncodedCertificate(v3, sha256WithRSAEncryption(),
|
||||
serialNumber, issuerDER,
|
||||
oneDayBeforeNow, oneDayAfterNow,
|
||||
subjectDER, *subjectKey, extensions,
|
||||
*subjectKey,
|
||||
sha256WithRSAEncryption());
|
||||
}
|
||||
|
||||
// Creates a self-signed certificate with the given extension.
|
||||
static ByteString
|
||||
CreateCertWithOneExtension(const char* subjectStr, const ByteString& extension)
|
||||
{
|
||||
const ByteString extensions[] = { extension, ByteString() };
|
||||
return CreateCertWithExtensions(subjectStr, extensions);
|
||||
}
|
||||
|
||||
class TrustEverythingTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
private:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
|
||||
/*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = TrustLevel::TrustAnchor;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv unknownExtensionOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3
|
||||
static const uint8_t tlv_unknownExtensionOID[] = {
|
||||
0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a,
|
||||
0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-pe-authorityInformationAccess 1.3.6.1.5.5.7.1.1
|
||||
static const uint8_t tlv_id_pe_authorityInformationAccess[] = {
|
||||
0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv wrongExtensionOID 1.3.6.6.1.5.5.7.1.1
|
||||
// (there is an extra "6" that shouldn't be in this OID)
|
||||
static const uint8_t tlv_wrongExtensionOID[] = {
|
||||
0x06, 0x09, 0x2b, 0x06, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-ce-unknown 2.5.29.55
|
||||
// (this is a made-up OID for testing "id-ce"-prefixed OIDs that mozilla::pkix
|
||||
// doesn't handle)
|
||||
static const uint8_t tlv_id_ce_unknown[] = {
|
||||
0x06, 0x03, 0x55, 0x1d, 0x37
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-ce-inhibitAnyPolicy 2.5.29.54
|
||||
static const uint8_t tlv_id_ce_inhibitAnyPolicy[] = {
|
||||
0x06, 0x03, 0x55, 0x1d, 0x36
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5
|
||||
static const uint8_t tlv_id_pkix_ocsp_nocheck[] = {
|
||||
0x06, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05
|
||||
};
|
||||
|
||||
struct ExtensionTestcase
|
||||
{
|
||||
ByteString extension;
|
||||
Result expectedResult;
|
||||
};
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os, const ExtensionTestcase&)
|
||||
{
|
||||
return os << "TODO (bug 1318770)";
|
||||
}
|
||||
|
||||
static const ExtensionTestcase EXTENSION_TESTCASES[] =
|
||||
{
|
||||
// Tests that a non-critical extension not in the id-ce or id-pe arcs (which
|
||||
// is thus unknown to us) verifies successfully even if empty (extensions we
|
||||
// know about aren't normally allowed to be empty).
|
||||
{ TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_unknownExtensionOID) +
|
||||
TLV(der::OCTET_STRING, ByteString())),
|
||||
Success
|
||||
},
|
||||
|
||||
// Tests that a critical extension not in the id-ce or id-pe arcs (which is
|
||||
// thus unknown to us) is detected and that verification fails with the
|
||||
// appropriate error.
|
||||
{ TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_unknownExtensionOID) +
|
||||
Boolean(true) +
|
||||
TLV(der::OCTET_STRING, ByteString())),
|
||||
Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
|
||||
},
|
||||
|
||||
// Tests that a id-pe-authorityInformationAccess critical extension
|
||||
// is detected and that verification succeeds.
|
||||
// XXX: According to RFC 5280 an AIA that consists of an empty sequence is
|
||||
// not legal, but we accept it and that is not what we're testing here.
|
||||
{ TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_pe_authorityInformationAccess) +
|
||||
Boolean(true) +
|
||||
TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ByteString()))),
|
||||
Success
|
||||
},
|
||||
|
||||
// Tests that an incorrect OID for id-pe-authorityInformationAccess
|
||||
// (when marked critical) is detected and that verification fails.
|
||||
// (Until bug 1020993 was fixed, this wrong value was used for
|
||||
// id-pe-authorityInformationAccess.)
|
||||
{ TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_wrongExtensionOID) +
|
||||
Boolean(true) +
|
||||
TLV(der::OCTET_STRING, ByteString())),
|
||||
Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
|
||||
},
|
||||
|
||||
// We know about some id-ce extensions (OID arc 2.5.29), but not all of them.
|
||||
// Tests that an unknown id-ce extension is detected and that verification
|
||||
// fails.
|
||||
{ TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_ce_unknown) +
|
||||
Boolean(true) +
|
||||
TLV(der::OCTET_STRING, ByteString())),
|
||||
Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
|
||||
},
|
||||
|
||||
// Tests that a certificate with a known critical id-ce extension (in this
|
||||
// case, OID 2.5.29.54, which is id-ce-inhibitAnyPolicy), verifies
|
||||
// successfully.
|
||||
{ TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_ce_inhibitAnyPolicy) +
|
||||
Boolean(true) +
|
||||
TLV(der::OCTET_STRING, Integer(0))),
|
||||
Success
|
||||
},
|
||||
|
||||
// Tests that a certificate with the id-pkix-ocsp-nocheck extension (marked
|
||||
// critical) verifies successfully.
|
||||
// RFC 6960:
|
||||
// ext-ocsp-nocheck EXTENSION ::= { SYNTAX NULL IDENTIFIED
|
||||
// BY id-pkix-ocsp-nocheck }
|
||||
{ TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_pkix_ocsp_nocheck) +
|
||||
Boolean(true) +
|
||||
TLV(der::OCTET_STRING, TLV(der::NULLTag, ByteString()))),
|
||||
Success
|
||||
},
|
||||
|
||||
// Tests that a certificate with another representation of the
|
||||
// id-pkix-ocsp-nocheck extension (marked critical) verifies successfully.
|
||||
// According to http://comments.gmane.org/gmane.ietf.x509/30947,
|
||||
// some code creates certificates where value of the extension is
|
||||
// an empty OCTET STRING.
|
||||
{ TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_pkix_ocsp_nocheck) +
|
||||
Boolean(true) +
|
||||
TLV(der::OCTET_STRING, ByteString())),
|
||||
Success
|
||||
},
|
||||
};
|
||||
|
||||
class pkixcert_extension
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<ExtensionTestcase>
|
||||
{
|
||||
protected:
|
||||
static TrustEverythingTrustDomain trustDomain;
|
||||
};
|
||||
|
||||
/*static*/ TrustEverythingTrustDomain pkixcert_extension::trustDomain;
|
||||
|
||||
TEST_P(pkixcert_extension, ExtensionHandledProperly)
|
||||
{
|
||||
const ExtensionTestcase& testcase(GetParam());
|
||||
const char* cn = "Cert Extension Test";
|
||||
ByteString cert(CreateCertWithOneExtension(cn, testcase.extension));
|
||||
ASSERT_FALSE(ENCODING_FAILED(cert));
|
||||
Input certInput;
|
||||
ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
|
||||
ASSERT_EQ(testcase.expectedResult,
|
||||
BuildCertChain(trustDomain, certInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(pkixcert_extension,
|
||||
pkixcert_extension,
|
||||
testing::ValuesIn(EXTENSION_TESTCASES));
|
||||
|
||||
// Two subjectAltNames must result in an error.
|
||||
TEST_F(pkixcert_extension, DuplicateSubjectAltName)
|
||||
{
|
||||
// python DottedOIDToCode.py --tlv id-ce-subjectAltName 2.5.29.17
|
||||
static const uint8_t tlv_id_ce_subjectAltName[] = {
|
||||
0x06, 0x03, 0x55, 0x1d, 0x11
|
||||
};
|
||||
|
||||
ByteString subjectAltName(
|
||||
TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_ce_subjectAltName) +
|
||||
TLV(der::OCTET_STRING, TLV(der::SEQUENCE, DNSName("example.com")))));
|
||||
static const ByteString extensions[] = { subjectAltName, subjectAltName,
|
||||
ByteString() };
|
||||
static const char* certCN = "Cert With Duplicate subjectAltName";
|
||||
ByteString cert(CreateCertWithExtensions(certCN, extensions));
|
||||
ASSERT_FALSE(ENCODING_FAILED(cert));
|
||||
Input certInput;
|
||||
ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
|
||||
ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID,
|
||||
BuildCertChain(trustDomain, certInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr/*stapledOCSPResponse*/));
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
#include "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
static ByteString
|
||||
CreateCert(const char* issuerCN,
|
||||
const char* subjectCN,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
const TestSignatureAlgorithm& signatureAlgorithm,
|
||||
/*out*/ ByteString& subjectDER)
|
||||
{
|
||||
static long serialNumberValue = 0;
|
||||
++serialNumberValue;
|
||||
ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
|
||||
EXPECT_FALSE(ENCODING_FAILED(serialNumber));
|
||||
|
||||
ByteString issuerDER(CNToDERName(issuerCN));
|
||||
EXPECT_FALSE(ENCODING_FAILED(issuerDER));
|
||||
subjectDER = CNToDERName(subjectCN);
|
||||
EXPECT_FALSE(ENCODING_FAILED(subjectDER));
|
||||
|
||||
ByteString extensions[2];
|
||||
if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
|
||||
extensions[0] =
|
||||
CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
|
||||
EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
|
||||
}
|
||||
|
||||
ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
|
||||
ByteString certDER(CreateEncodedCertificate(v3, signatureAlgorithm,
|
||||
serialNumber, issuerDER,
|
||||
oneDayBeforeNow, oneDayAfterNow,
|
||||
subjectDER, *reusedKey,
|
||||
extensions, *reusedKey,
|
||||
signatureAlgorithm));
|
||||
EXPECT_FALSE(ENCODING_FAILED(certDER));
|
||||
return certDER;
|
||||
}
|
||||
|
||||
class AlgorithmTestsTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
AlgorithmTestsTrustDomain(const ByteString& aRootDER,
|
||||
const ByteString& aRootSubjectDER,
|
||||
/*optional*/ const ByteString& aIntDER,
|
||||
/*optional*/ const ByteString& aIntSubjectDER)
|
||||
: rootDER(aRootDER)
|
||||
, rootSubjectDER(aRootSubjectDER)
|
||||
, intDER(aIntDER)
|
||||
, intSubjectDER(aIntSubjectDER)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
|
||||
/*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
if (InputEqualsByteString(candidateCert, rootDER)) {
|
||||
trustLevel = TrustLevel::TrustAnchor;
|
||||
} else {
|
||||
trustLevel = TrustLevel::InheritsTrust;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
|
||||
override
|
||||
{
|
||||
ByteString* issuerDER = nullptr;
|
||||
if (InputEqualsByteString(encodedIssuerName, rootSubjectDER)) {
|
||||
issuerDER = &rootDER;
|
||||
} else if (InputEqualsByteString(encodedIssuerName, intSubjectDER)) {
|
||||
issuerDER = &intDER;
|
||||
} else {
|
||||
// FindIssuer just returns success if it can't find a potential issuer.
|
||||
return Success;
|
||||
}
|
||||
Input issuerCert;
|
||||
Result rv = issuerCert.Init(issuerDER->data(), issuerDER->length());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
bool keepGoing;
|
||||
return checker.Check(issuerCert, nullptr, keepGoing);
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
const Input*, const Input*) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
ByteString rootDER;
|
||||
ByteString rootSubjectDER;
|
||||
ByteString intDER;
|
||||
ByteString intSubjectDER;
|
||||
};
|
||||
|
||||
static const TestSignatureAlgorithm NO_INTERMEDIATE
|
||||
{
|
||||
TestPublicKeyAlgorithm(ByteString()),
|
||||
TestDigestAlgorithmID::MD2,
|
||||
ByteString(),
|
||||
false
|
||||
};
|
||||
|
||||
struct ChainValidity final
|
||||
{
|
||||
ChainValidity(const TestSignatureAlgorithm& aEndEntitySignatureAlgorithm,
|
||||
const TestSignatureAlgorithm& aOptionalIntSignatureAlgorithm,
|
||||
const TestSignatureAlgorithm& aRootSignatureAlgorithm,
|
||||
bool aIsValid)
|
||||
: endEntitySignatureAlgorithm(aEndEntitySignatureAlgorithm)
|
||||
, optionalIntermediateSignatureAlgorithm(aOptionalIntSignatureAlgorithm)
|
||||
, rootSignatureAlgorithm(aRootSignatureAlgorithm)
|
||||
, isValid(aIsValid)
|
||||
{ }
|
||||
|
||||
// In general, a certificate is generated for each of these. However, if
|
||||
// optionalIntermediateSignatureAlgorithm is NO_INTERMEDIATE, then only 2
|
||||
// certificates are generated.
|
||||
// The certificate generated for the given rootSignatureAlgorithm is the
|
||||
// trust anchor.
|
||||
TestSignatureAlgorithm endEntitySignatureAlgorithm;
|
||||
TestSignatureAlgorithm optionalIntermediateSignatureAlgorithm;
|
||||
TestSignatureAlgorithm rootSignatureAlgorithm;
|
||||
bool isValid;
|
||||
};
|
||||
|
||||
static const ChainValidity CHAIN_VALIDITY[] =
|
||||
{
|
||||
// The trust anchor may have a signature with an unsupported signature
|
||||
// algorithm.
|
||||
ChainValidity(sha256WithRSAEncryption(),
|
||||
NO_INTERMEDIATE,
|
||||
md5WithRSAEncryption(),
|
||||
true),
|
||||
ChainValidity(sha256WithRSAEncryption(),
|
||||
NO_INTERMEDIATE,
|
||||
md2WithRSAEncryption(),
|
||||
true),
|
||||
|
||||
// Certificates that are not trust anchors must not have a signature with an
|
||||
// unsupported signature algorithm.
|
||||
ChainValidity(md5WithRSAEncryption(),
|
||||
NO_INTERMEDIATE,
|
||||
sha256WithRSAEncryption(),
|
||||
false),
|
||||
ChainValidity(md2WithRSAEncryption(),
|
||||
NO_INTERMEDIATE,
|
||||
sha256WithRSAEncryption(),
|
||||
false),
|
||||
ChainValidity(md2WithRSAEncryption(),
|
||||
NO_INTERMEDIATE,
|
||||
md5WithRSAEncryption(),
|
||||
false),
|
||||
ChainValidity(sha256WithRSAEncryption(),
|
||||
md5WithRSAEncryption(),
|
||||
sha256WithRSAEncryption(),
|
||||
false),
|
||||
ChainValidity(sha256WithRSAEncryption(),
|
||||
md2WithRSAEncryption(),
|
||||
sha256WithRSAEncryption(),
|
||||
false),
|
||||
ChainValidity(sha256WithRSAEncryption(),
|
||||
md2WithRSAEncryption(),
|
||||
md5WithRSAEncryption(),
|
||||
false),
|
||||
};
|
||||
|
||||
class pkixcert_IsValidChainForAlgorithm
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<ChainValidity>
|
||||
{
|
||||
};
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os,
|
||||
const pkixcert_IsValidChainForAlgorithm&)
|
||||
{
|
||||
return os << "TODO (bug 1318770)";
|
||||
}
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os, const ChainValidity&)
|
||||
{
|
||||
return os << "TODO (bug 1318770)";
|
||||
}
|
||||
|
||||
TEST_P(pkixcert_IsValidChainForAlgorithm, IsValidChainForAlgorithm)
|
||||
{
|
||||
const ChainValidity& chainValidity(GetParam());
|
||||
const char* rootCN = "CN=Root";
|
||||
ByteString rootSubjectDER;
|
||||
ByteString rootEncoded(
|
||||
CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA,
|
||||
chainValidity.rootSignatureAlgorithm, rootSubjectDER));
|
||||
EXPECT_FALSE(ENCODING_FAILED(rootEncoded));
|
||||
EXPECT_FALSE(ENCODING_FAILED(rootSubjectDER));
|
||||
|
||||
const char* issuerCN = rootCN;
|
||||
|
||||
const char* intermediateCN = "CN=Intermediate";
|
||||
ByteString intermediateSubjectDER;
|
||||
ByteString intermediateEncoded;
|
||||
|
||||
// If the the algorithmIdentifier is empty, then it's NO_INTERMEDIATE.
|
||||
if (!chainValidity.optionalIntermediateSignatureAlgorithm
|
||||
.algorithmIdentifier.empty()) {
|
||||
intermediateEncoded =
|
||||
CreateCert(rootCN, intermediateCN, EndEntityOrCA::MustBeCA,
|
||||
chainValidity.optionalIntermediateSignatureAlgorithm,
|
||||
intermediateSubjectDER);
|
||||
EXPECT_FALSE(ENCODING_FAILED(intermediateEncoded));
|
||||
EXPECT_FALSE(ENCODING_FAILED(intermediateSubjectDER));
|
||||
issuerCN = intermediateCN;
|
||||
}
|
||||
|
||||
AlgorithmTestsTrustDomain trustDomain(rootEncoded, rootSubjectDER,
|
||||
intermediateEncoded,
|
||||
intermediateSubjectDER);
|
||||
|
||||
const char* endEntityCN = "CN=End Entity";
|
||||
ByteString endEntitySubjectDER;
|
||||
ByteString endEntityEncoded(
|
||||
CreateCert(issuerCN, endEntityCN, EndEntityOrCA::MustBeEndEntity,
|
||||
chainValidity.endEntitySignatureAlgorithm,
|
||||
endEntitySubjectDER));
|
||||
EXPECT_FALSE(ENCODING_FAILED(endEntityEncoded));
|
||||
EXPECT_FALSE(ENCODING_FAILED(endEntitySubjectDER));
|
||||
|
||||
Input endEntity;
|
||||
ASSERT_EQ(Success, endEntity.Init(endEntityEncoded.data(),
|
||||
endEntityEncoded.length()));
|
||||
Result expectedResult = chainValidity.isValid
|
||||
? Success
|
||||
: Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
|
||||
ASSERT_EQ(expectedResult,
|
||||
BuildCertChain(trustDomain, endEntity, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy, nullptr));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(pkixcert_IsValidChainForAlgorithm,
|
||||
pkixcert_IsValidChainForAlgorithm,
|
||||
testing::ValuesIn(CHAIN_VALIDITY));
|
|
@ -0,0 +1,722 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2016 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
#include "mozpkix/pkixutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
extern Result CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
|
||||
const Input* encodedExtendedKeyUsage,
|
||||
KeyPurposeId requiredEKU,
|
||||
TrustDomain& trustDomain, Time notBefore);
|
||||
|
||||
} } // namespace mozilla::pkix
|
||||
|
||||
class pkixcheck_CheckExtendedKeyUsage : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
DefaultCryptoTrustDomain mTrustDomain;
|
||||
};
|
||||
|
||||
#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_CERT_TYPE, x)
|
||||
|
||||
// tlv_id_kp_OCSPSigning and tlv_id_kp_serverAuth are defined in pkixtestutil.h
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-kp-clientAuth 1.3.6.1.5.5.7.3.2
|
||||
static const uint8_t tlv_id_kp_clientAuth[] = {
|
||||
0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-kp-codeSigning 1.3.6.1.5.5.7.3.3
|
||||
static const uint8_t tlv_id_kp_codeSigning[] = {
|
||||
0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv id_kp_emailProtection 1.3.6.1.5.5.7.3.4
|
||||
static const uint8_t tlv_id_kp_emailProtection[] = {
|
||||
0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-Netscape-stepUp 2.16.840.1.113730.4.1
|
||||
static const uint8_t tlv_id_Netscape_stepUp[] = {
|
||||
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv unknownOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3
|
||||
static const uint8_t tlv_unknownOID[] = {
|
||||
0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a,
|
||||
0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv anyExtendedKeyUsage 2.5.29.37.0
|
||||
static const uint8_t tlv_anyExtendedKeyUsage[] = {
|
||||
0x06, 0x04, 0x55, 0x1d, 0x25, 0x00
|
||||
};
|
||||
|
||||
TEST_F(pkixcheck_CheckExtendedKeyUsage, none)
|
||||
{
|
||||
// The input Input is nullptr. This means the cert had no extended key usage
|
||||
// extension. This is always valid except for when the certificate is an
|
||||
// end-entity and the required usage is id-kp-OCSPSigning.
|
||||
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr,
|
||||
KeyPurposeId::id_kp_clientAuth,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
|
||||
KeyPurposeId::id_kp_clientAuth,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr,
|
||||
KeyPurposeId::id_kp_codeSigning,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
|
||||
KeyPurposeId::id_kp_codeSigning,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr,
|
||||
KeyPurposeId::id_kp_emailProtection,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
|
||||
KeyPurposeId::id_kp_emailProtection,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyPurposeId::id_kp_OCSPSigning,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
|
||||
KeyPurposeId::id_kp_OCSPSigning,
|
||||
mTrustDomain, Now()));
|
||||
}
|
||||
|
||||
static const Input empty_null;
|
||||
|
||||
TEST_F(pkixcheck_CheckExtendedKeyUsage, empty)
|
||||
{
|
||||
// The input Input is empty. The cert has an empty extended key usage
|
||||
// extension, which is syntactically invalid.
|
||||
ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_null,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
mTrustDomain, Now()));
|
||||
|
||||
static const uint8_t dummy = 0x00;
|
||||
Input empty_nonnull;
|
||||
ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
|
||||
ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull,
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
mTrustDomain, Now()));
|
||||
}
|
||||
|
||||
struct EKUTestcase
|
||||
{
|
||||
ByteString ekuSEQUENCE;
|
||||
KeyPurposeId keyPurposeId;
|
||||
Result expectedResultEndEntity;
|
||||
Result expectedResultCA;
|
||||
};
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os, const EKUTestcase&)
|
||||
{
|
||||
return os << "TODO (bug 1318770)";
|
||||
}
|
||||
|
||||
class CheckExtendedKeyUsageTest
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<EKUTestcase>
|
||||
{
|
||||
protected:
|
||||
DefaultCryptoTrustDomain mTrustDomain;
|
||||
};
|
||||
|
||||
TEST_P(CheckExtendedKeyUsageTest, EKUTestcase)
|
||||
{
|
||||
const EKUTestcase& param(GetParam());
|
||||
Input encodedEKU;
|
||||
ASSERT_EQ(Success, encodedEKU.Init(param.ekuSEQUENCE.data(),
|
||||
param.ekuSEQUENCE.length()));
|
||||
ASSERT_EQ(param.expectedResultEndEntity,
|
||||
CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &encodedEKU,
|
||||
param.keyPurposeId,
|
||||
mTrustDomain, Now()));
|
||||
ASSERT_EQ(param.expectedResultCA,
|
||||
CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &encodedEKU,
|
||||
param.keyPurposeId,
|
||||
mTrustDomain, Now()));
|
||||
}
|
||||
|
||||
#define SINGLE_EKU_SUCCESS(oidBytes, keyPurposeId) \
|
||||
{ TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
|
||||
Success, Success }
|
||||
#define SINGLE_EKU_SUCCESS_CA(oidBytes, keyPurposeId) \
|
||||
{ TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE, Success }
|
||||
#define SINGLE_EKU_FAILURE(oidBytes, keyPurposeId) \
|
||||
{ TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE }
|
||||
#define DOUBLE_EKU_SUCCESS(oidBytes1, oidBytes2, keyPurposeId) \
|
||||
{ TLV(der::SEQUENCE, \
|
||||
BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
|
||||
keyPurposeId, \
|
||||
Success, Success }
|
||||
#define DOUBLE_EKU_SUCCESS_CA(oidBytes1, oidBytes2, keyPurposeId) \
|
||||
{ TLV(der::SEQUENCE, \
|
||||
BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
|
||||
keyPurposeId, \
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE, Success }
|
||||
#define DOUBLE_EKU_FAILURE(oidBytes1, oidBytes2, keyPurposeId) \
|
||||
{ TLV(der::SEQUENCE, \
|
||||
BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
|
||||
keyPurposeId, \
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE }
|
||||
|
||||
static const EKUTestcase EKU_TESTCASES[] =
|
||||
{
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::anyExtendedKeyUsage),
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_serverAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_clientAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_codeSigning),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_emailProtection),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth),
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
// For end-entities, if id-kp-OCSPSigning is present, no usage is allowed
|
||||
// except OCSPSigning.
|
||||
SINGLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
|
||||
SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
|
||||
SINGLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
SINGLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
|
||||
// For compatibility, id-Netscape-stepUp is treated as equivalent to
|
||||
// id-kp-serverAuth for CAs.
|
||||
SINGLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
|
||||
SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
|
||||
SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
SINGLE_EKU_SUCCESS(tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
|
||||
SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
|
||||
SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
|
||||
SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
SINGLE_EKU_SUCCESS(tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
|
||||
SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
|
||||
SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
|
||||
SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
|
||||
SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
|
||||
|
||||
DOUBLE_EKU_SUCCESS(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
|
||||
DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
|
||||
DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
|
||||
DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
|
||||
DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage,
|
||||
CheckExtendedKeyUsageTest,
|
||||
::testing::ValuesIn(EKU_TESTCASES));
|
||||
|
||||
struct EKUChainTestcase
|
||||
{
|
||||
ByteString ekuExtensionEE;
|
||||
ByteString ekuExtensionCA;
|
||||
KeyPurposeId keyPurposeId;
|
||||
Result expectedResult;
|
||||
};
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os, const EKUChainTestcase&)
|
||||
{
|
||||
return os << "TODO (bug 1318770)";
|
||||
}
|
||||
|
||||
class CheckExtendedKeyUsageChainTest
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<EKUChainTestcase>
|
||||
{
|
||||
};
|
||||
|
||||
static ByteString
|
||||
CreateCert(const char* issuerCN, const char* subjectCN,
|
||||
EndEntityOrCA endEntityOrCA, ByteString encodedEKU)
|
||||
{
|
||||
static long serialNumberValue = 0;
|
||||
++serialNumberValue;
|
||||
ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
|
||||
EXPECT_FALSE(ENCODING_FAILED(serialNumber));
|
||||
|
||||
ByteString issuerDER(CNToDERName(issuerCN));
|
||||
ByteString subjectDER(CNToDERName(subjectCN));
|
||||
|
||||
ByteString extensions[3];
|
||||
extensions[0] =
|
||||
CreateEncodedBasicConstraints(endEntityOrCA == EndEntityOrCA::MustBeCA,
|
||||
nullptr, Critical::Yes);
|
||||
EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
|
||||
if (encodedEKU.length() > 0) {
|
||||
extensions[1] = encodedEKU;
|
||||
}
|
||||
|
||||
ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
|
||||
ByteString certDER(CreateEncodedCertificate(
|
||||
v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
|
||||
oneDayBeforeNow, oneDayAfterNow, subjectDER,
|
||||
*reusedKey, extensions, *reusedKey,
|
||||
sha256WithRSAEncryption()));
|
||||
EXPECT_FALSE(ENCODING_FAILED(certDER));
|
||||
|
||||
return certDER;
|
||||
}
|
||||
|
||||
class EKUTrustDomain final : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit EKUTrustDomain(ByteString issuerCertDER)
|
||||
: mIssuerCertDER(issuerCertDER)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
|
||||
TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = InputEqualsByteString(candidateCert, mIssuerCertDER)
|
||||
? TrustLevel::TrustAnchor
|
||||
: TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker& checker, Time) override
|
||||
{
|
||||
Input derCert;
|
||||
Result rv = derCert.Init(mIssuerCertDER.data(), mIssuerCertDER.length());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
bool keepGoing;
|
||||
return checker.Check(derCert, nullptr, keepGoing);
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
const Input*, const Input*) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
ByteString mIssuerCertDER;
|
||||
};
|
||||
|
||||
TEST_P(CheckExtendedKeyUsageChainTest, EKUChainTestcase)
|
||||
{
|
||||
const EKUChainTestcase& param(GetParam());
|
||||
ByteString issuerCertDER(CreateCert("CA", "CA", EndEntityOrCA::MustBeCA,
|
||||
param.ekuExtensionCA));
|
||||
ByteString subjectCertDER(CreateCert("CA", "EE",
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
param.ekuExtensionEE));
|
||||
|
||||
EKUTrustDomain trustDomain(issuerCertDER);
|
||||
|
||||
Input subjectCertDERInput;
|
||||
ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(),
|
||||
subjectCertDER.length()));
|
||||
ASSERT_EQ(param.expectedResult,
|
||||
BuildCertChain(trustDomain, subjectCertDERInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
param.keyPurposeId,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37
|
||||
static const uint8_t tlv_id_ce_extKeyUsage[] = {
|
||||
0x06, 0x03, 0x55, 0x1d, 0x25
|
||||
};
|
||||
|
||||
static inline ByteString
|
||||
CreateEKUExtension(ByteString ekuOIDs)
|
||||
{
|
||||
return TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_ce_extKeyUsage) +
|
||||
TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ekuOIDs)));
|
||||
}
|
||||
|
||||
static const EKUChainTestcase EKU_CHAIN_TESTCASES[] =
|
||||
{
|
||||
{
|
||||
// Both end-entity and CA have id-kp-serverAuth => should succeed
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Success
|
||||
},
|
||||
{
|
||||
// CA has no EKU extension => should succeed
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
|
||||
ByteString(),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Success
|
||||
},
|
||||
{
|
||||
// End-entity has no EKU extension => should succeed
|
||||
ByteString(),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Success
|
||||
},
|
||||
{
|
||||
// No EKU extensions at all => should succeed
|
||||
ByteString(),
|
||||
ByteString(),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Success
|
||||
},
|
||||
{
|
||||
// CA has EKU without id-kp-serverAuth => should fail
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE
|
||||
},
|
||||
{
|
||||
// End-entity has EKU without id-kp-serverAuth => should fail
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE
|
||||
},
|
||||
{
|
||||
// Both end-entity and CA have EKU without id-kp-serverAuth => should fail
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE
|
||||
},
|
||||
{
|
||||
// End-entity has no EKU, CA doesn't have id-kp-serverAuth => should fail
|
||||
ByteString(),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE
|
||||
},
|
||||
{
|
||||
// End-entity doesn't have id-kp-serverAuth, CA has no EKU => should fail
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
ByteString(),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE
|
||||
},
|
||||
{
|
||||
// CA has id-Netscape-stepUp => should succeed
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Success
|
||||
},
|
||||
{
|
||||
// End-entity has id-Netscape-stepUp => should fail
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE
|
||||
},
|
||||
{
|
||||
// End-entity and CA have id-kp-serverAuth and id-kp-clientAuth => should
|
||||
// succeed
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
|
||||
BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
|
||||
BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Success
|
||||
},
|
||||
{
|
||||
// End-entity has id-kp-serverAuth and id-kp-OCSPSigning => should fail
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
|
||||
BytesToByteString(tlv_id_kp_OCSPSigning)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
|
||||
BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Result::ERROR_INADEQUATE_CERT_TYPE
|
||||
},
|
||||
{
|
||||
// CA has id-kp-serverAuth and id-kp-OCSPSigning => should succeed
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
|
||||
BytesToByteString(tlv_id_kp_clientAuth)),
|
||||
CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
|
||||
BytesToByteString(tlv_id_kp_OCSPSigning)),
|
||||
KeyPurposeId::id_kp_serverAuth,
|
||||
Success
|
||||
},
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage,
|
||||
CheckExtendedKeyUsageChainTest,
|
||||
::testing::ValuesIn(EKU_CHAIN_TESTCASES));
|
|
@ -0,0 +1,63 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2016 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixcheck.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
class pkixcheck_CheckIssuer : public ::testing::Test { };
|
||||
|
||||
static const uint8_t EMPTY_NAME_DATA[] = {
|
||||
0x30, 0x00 /* tag, length */
|
||||
};
|
||||
static const Input EMPTY_NAME(EMPTY_NAME_DATA);
|
||||
|
||||
static const uint8_t VALID_NAME_DATA[] = {
|
||||
/* From https://www.example.com/: C=US, O=DigiCert Inc, OU=www.digicert.com,
|
||||
* CN=DigiCert SHA2 High Assurance Server CA */
|
||||
0x30, 0x70, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
||||
0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0A,
|
||||
0x13, 0x0C, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49,
|
||||
0x6E, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13,
|
||||
0x10, 0x77, 0x77, 0x77, 0x2E, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72,
|
||||
0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x31, 0x2F, 0x30, 0x2D, 0x06, 0x03, 0x55,
|
||||
0x04, 0x03, 0x13, 0x26, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
|
||||
0x20, 0x53, 0x48, 0x41, 0x32, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41,
|
||||
0x73, 0x73, 0x75, 0x72, 0x61, 0x6E, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x20, 0x43, 0x41
|
||||
};
|
||||
static const Input VALID_NAME(VALID_NAME_DATA);
|
||||
|
||||
TEST_F(pkixcheck_CheckIssuer, ValidIssuer)
|
||||
{
|
||||
ASSERT_EQ(Success, CheckIssuer(VALID_NAME));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckIssuer, EmptyIssuer)
|
||||
{
|
||||
ASSERT_EQ(Result::ERROR_EMPTY_ISSUER_NAME, CheckIssuer(EMPTY_NAME));
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
extern Result CheckKeyUsage(EndEntityOrCA endEntityOrCA,
|
||||
const Input* encodedKeyUsage,
|
||||
KeyUsage requiredKeyUsageIfPresent);
|
||||
|
||||
} } // namespace mozilla::pkix
|
||||
|
||||
class pkixcheck_CheckKeyUsage : public ::testing::Test { };
|
||||
|
||||
#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_KEY_USAGE, x)
|
||||
|
||||
// Make it easy to define test data for the common, simplest cases.
|
||||
#define NAMED_SIMPLE_KU(name, unusedBits, bits) \
|
||||
const uint8_t name##_bytes[4] = { \
|
||||
0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, unusedBits, bits \
|
||||
}; \
|
||||
const Input name(name##_bytes);
|
||||
|
||||
static const Input empty_null;
|
||||
|
||||
// Note that keyCertSign is really the only interesting case for CA
|
||||
// certificates since we don't support cRLSign.
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, EE_none)
|
||||
{
|
||||
// The input Input is nullptr. This means the cert had no keyUsage
|
||||
// extension. This is always valid because no key usage in an end-entity
|
||||
// means that there are no key usage restrictions.
|
||||
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::noParticularKeyUsageRequired));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::nonRepudiation));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::keyEncipherment));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::dataEncipherment));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
|
||||
KeyUsage::keyAgreement));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, EE_empty)
|
||||
{
|
||||
// The input Input is empty. The cert had an empty keyUsage extension,
|
||||
// which is syntactically invalid.
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null,
|
||||
KeyUsage::digitalSignature));
|
||||
static const uint8_t dummy = 0x00;
|
||||
Input empty_nonnull;
|
||||
ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull,
|
||||
KeyUsage::digitalSignature));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, CA_none)
|
||||
{
|
||||
// A CA certificate does not have a KU extension.
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
|
||||
KeyUsage::keyCertSign));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, CA_empty)
|
||||
{
|
||||
// A CA certificate has an empty KU extension.
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_null,
|
||||
KeyUsage::keyCertSign));
|
||||
static const uint8_t dummy = 0x00;
|
||||
Input empty_nonnull;
|
||||
ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull,
|
||||
KeyUsage::keyCertSign));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, maxUnusedBits)
|
||||
{
|
||||
NAMED_SIMPLE_KU(encoded, 7, 0x80);
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &encoded,
|
||||
KeyUsage::digitalSignature));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, tooManyUnusedBits)
|
||||
{
|
||||
static uint8_t oneValueByteData[] = {
|
||||
0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 8/*unused bits*/, 0x80
|
||||
};
|
||||
static const Input oneValueByte(oneValueByteData);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
|
||||
static uint8_t twoValueBytesData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 8/*unused bits*/, 0x01, 0x00
|
||||
};
|
||||
static const Input twoValueBytes(twoValueBytesData);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_NoPaddingBits)
|
||||
{
|
||||
static const uint8_t DER_BYTES[] = {
|
||||
0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 0/*unused bits*/
|
||||
};
|
||||
static const Input DER(DER_BYTES);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER,
|
||||
KeyUsage::keyCertSign));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_7PaddingBits)
|
||||
{
|
||||
static const uint8_t DER_BYTES[] = {
|
||||
0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 7/*unused bits*/
|
||||
};
|
||||
static const Input DER(DER_BYTES);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER,
|
||||
KeyUsage::keyCertSign));
|
||||
}
|
||||
|
||||
void ASSERT_SimpleCase(uint8_t unusedBits, uint8_t bits, KeyUsage usage)
|
||||
{
|
||||
// Test that only the right bit is accepted for the usage for both EE and CA
|
||||
// certs.
|
||||
NAMED_SIMPLE_KU(good, unusedBits, bits);
|
||||
ASSERT_EQ(Success,
|
||||
CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good, usage));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, &good, usage));
|
||||
|
||||
// We use (~bits >> unusedBits) << unusedBits) instead of using the same
|
||||
// calculation that is in CheckKeyUsage to validate that the calculation in
|
||||
// CheckKeyUsage is correct.
|
||||
|
||||
// Test that none of the other non-padding bits are mistaken for the given
|
||||
// key usage in the single-byte value case.
|
||||
NAMED_SIMPLE_KU(notGood, unusedBits,
|
||||
static_cast<uint8_t>((~bits >> unusedBits) << unusedBits));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, ¬Good, usage));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, ¬Good, usage));
|
||||
|
||||
// Test that none of the other non-padding bits are mistaken for the given
|
||||
// key usage in the two-byte value case.
|
||||
const uint8_t twoByteNotGoodData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, unusedBits,
|
||||
static_cast<uint8_t>(~bits),
|
||||
static_cast<uint8_t>((0xFFu >> unusedBits) << unusedBits)
|
||||
};
|
||||
Input twoByteNotGood(twoByteNotGoodData);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood,
|
||||
usage));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood, usage));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, simpleCases)
|
||||
{
|
||||
ASSERT_SimpleCase(7, 0x80, KeyUsage::digitalSignature);
|
||||
ASSERT_SimpleCase(6, 0x40, KeyUsage::nonRepudiation);
|
||||
ASSERT_SimpleCase(5, 0x20, KeyUsage::keyEncipherment);
|
||||
ASSERT_SimpleCase(4, 0x10, KeyUsage::dataEncipherment);
|
||||
ASSERT_SimpleCase(3, 0x08, KeyUsage::keyAgreement);
|
||||
}
|
||||
|
||||
// Only CAs are allowed to assert keyCertSign.
|
||||
// End-entity certs may assert it along with other key usages if keyCertSign
|
||||
// isn't the required key usage. This is for compatibility.
|
||||
TEST_F(pkixcheck_CheckKeyUsage, keyCertSign)
|
||||
{
|
||||
NAMED_SIMPLE_KU(good, 2, 0x04);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good,
|
||||
KeyUsage::keyCertSign));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, &good,
|
||||
KeyUsage::keyCertSign));
|
||||
|
||||
// Test that none of the other non-padding bits are mistaken for the given
|
||||
// key usage in the one-byte value case.
|
||||
NAMED_SIMPLE_KU(notGood, 2, 0xFB);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, ¬Good,
|
||||
KeyUsage::keyCertSign));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, ¬Good,
|
||||
KeyUsage::keyCertSign));
|
||||
|
||||
// Test that none of the other non-padding bits are mistaken for the given
|
||||
// key usage in the two-byte value case.
|
||||
static uint8_t twoByteNotGoodData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 2/*unused bits*/, 0xFBu, 0xFCu
|
||||
};
|
||||
static const Input twoByteNotGood(twoByteNotGoodData);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood,
|
||||
KeyUsage::keyCertSign));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood,
|
||||
KeyUsage::keyCertSign));
|
||||
|
||||
// If an end-entity certificate does assert keyCertSign, this is allowed
|
||||
// as long as that isn't the required key usage.
|
||||
NAMED_SIMPLE_KU(digitalSignatureAndKeyCertSign, 2, 0x84);
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
&digitalSignatureAndKeyCertSign,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
&digitalSignatureAndKeyCertSign,
|
||||
KeyUsage::keyCertSign));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckKeyUsage, unusedBitNotZero)
|
||||
{
|
||||
// single byte control case
|
||||
static uint8_t controlOneValueByteData[] = {
|
||||
0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80
|
||||
};
|
||||
static const Input controlOneValueByte(controlOneValueByteData);
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
&controlOneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA,
|
||||
&controlOneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
|
||||
// single-byte test case
|
||||
static uint8_t oneValueByteData[] = {
|
||||
0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80 | 0x01
|
||||
};
|
||||
static const Input oneValueByte(oneValueByteData);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &oneValueByte,
|
||||
KeyUsage::digitalSignature));
|
||||
|
||||
// two-byte control case
|
||||
static uint8_t controlTwoValueBytesData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/,
|
||||
0x80 | 0x01, 0x80
|
||||
};
|
||||
static const Input controlTwoValueBytes(controlTwoValueBytesData);
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
|
||||
&controlTwoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA,
|
||||
&controlTwoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
|
||||
// two-byte test case
|
||||
static uint8_t twoValueBytesData[] = {
|
||||
0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/,
|
||||
0x80 | 0x01, 0x80 | 0x01
|
||||
};
|
||||
static const Input twoValueBytes(twoValueBytesData);
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoValueBytes,
|
||||
KeyUsage::digitalSignature));
|
||||
}
|
|
@ -0,0 +1,367 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2015 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
extern Result CheckSignatureAlgorithm(
|
||||
TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA,
|
||||
Time notBefore,
|
||||
const der::SignedDataWithSignature& signedData,
|
||||
Input signatureValue);
|
||||
|
||||
} } // namespace mozilla::pkix
|
||||
|
||||
struct CheckSignatureAlgorithmTestParams
|
||||
{
|
||||
ByteString signatureAlgorithmValue;
|
||||
ByteString signatureValue;
|
||||
unsigned int signatureLengthInBytes;
|
||||
Result expectedResult;
|
||||
};
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os,
|
||||
const CheckSignatureAlgorithmTestParams&)
|
||||
{
|
||||
return os << "TODO (bug 1318770)";
|
||||
}
|
||||
|
||||
#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s))
|
||||
|
||||
// python DottedOIDToCode.py --tlv sha256WithRSAEncryption 1.2.840.113549.1.1.11
|
||||
static const uint8_t tlv_sha256WithRSAEncryption[] = {
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b
|
||||
};
|
||||
|
||||
// Same as tlv_sha256WithRSAEncryption, except one without the "0x0b" and with
|
||||
// the DER length decreased accordingly.
|
||||
static const uint8_t tlv_sha256WithRSAEncryption_truncated[] = {
|
||||
0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv sha-1WithRSAEncryption 1.2.840.113549.1.1.5
|
||||
static const uint8_t tlv_sha_1WithRSAEncryption[] = {
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv sha1WithRSASignature 1.3.14.3.2.29
|
||||
static const uint8_t tlv_sha1WithRSASignature[] = {
|
||||
0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1d
|
||||
};
|
||||
|
||||
// python DottedOIDToCode.py --tlv md5WithRSAEncryption 1.2.840.113549.1.1.4
|
||||
static const uint8_t tlv_md5WithRSAEncryption[] = {
|
||||
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04
|
||||
};
|
||||
|
||||
static const CheckSignatureAlgorithmTestParams
|
||||
CHECKSIGNATUREALGORITHM_TEST_PARAMS[] =
|
||||
{
|
||||
{ // Both algorithm IDs are empty
|
||||
ByteString(),
|
||||
ByteString(),
|
||||
2048 / 8,
|
||||
Result::ERROR_BAD_DER,
|
||||
},
|
||||
{ // signatureAlgorithm is empty, signature is supported.
|
||||
ByteString(),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_BAD_DER,
|
||||
},
|
||||
{ // signatureAlgorithm is supported, signature is empty.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
ByteString(),
|
||||
2048 / 8,
|
||||
Result::ERROR_BAD_DER,
|
||||
},
|
||||
{ // Algorithms match, both are supported.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Success
|
||||
},
|
||||
{ // Algorithms do not match because signatureAlgorithm is truncated.
|
||||
BS(tlv_sha256WithRSAEncryption_truncated),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // Algorithms do not match because signature is truncated.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption_truncated),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // Algorithms do not match, both are supported.
|
||||
BS(tlv_sha_1WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
|
||||
},
|
||||
{ // Algorithms do not match, both are supported.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha_1WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
|
||||
},
|
||||
{ // Algorithms match, both are unsupported.
|
||||
BS(tlv_md5WithRSAEncryption),
|
||||
BS(tlv_md5WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // signatureAlgorithm is unsupported, signature is supported.
|
||||
BS(tlv_md5WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // signatureAlgorithm is supported, signature is unsupported.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_md5WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
|
||||
},
|
||||
{ // Both have the optional NULL parameter.
|
||||
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
|
||||
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
|
||||
2048 / 8,
|
||||
Success
|
||||
},
|
||||
{ // signatureAlgorithm has the optional NULL parameter, signature doesn't.
|
||||
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Success
|
||||
},
|
||||
{ // signatureAlgorithm does not have the optional NULL parameter, signature
|
||||
// does.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
|
||||
2048 / 8,
|
||||
Success
|
||||
},
|
||||
{ // The different OIDs for RSA-with-SHA1 we support are semantically
|
||||
// equivalent.
|
||||
BS(tlv_sha1WithRSASignature),
|
||||
BS(tlv_sha_1WithRSAEncryption),
|
||||
2048 / 8,
|
||||
Success,
|
||||
},
|
||||
{ // The different OIDs for RSA-with-SHA1 we support are semantically
|
||||
// equivalent (opposite order).
|
||||
BS(tlv_sha_1WithRSAEncryption),
|
||||
BS(tlv_sha1WithRSASignature),
|
||||
2048 / 8,
|
||||
Success,
|
||||
},
|
||||
{ // Algorithms match, both are supported, key size is not a multile of 128
|
||||
// bits. This test verifies that we're not wrongly rounding up the
|
||||
// signature size like we did in the original patch for bug 1131767.
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
BS(tlv_sha256WithRSAEncryption),
|
||||
(2048 / 8) - 1,
|
||||
Success
|
||||
},
|
||||
};
|
||||
|
||||
class pkixcheck_CheckSignatureAlgorithm
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<CheckSignatureAlgorithmTestParams>
|
||||
{
|
||||
};
|
||||
|
||||
class pkixcheck_CheckSignatureAlgorithm_TrustDomain final
|
||||
: public EverythingFailsByDefaultTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit pkixcheck_CheckSignatureAlgorithm_TrustDomain(
|
||||
unsigned int aPublicKeySizeInBits)
|
||||
: publicKeySizeInBits(aPublicKeySizeInBits)
|
||||
, checkedDigestAlgorithm(false)
|
||||
, checkedModulusSizeInBits(false)
|
||||
{
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time)
|
||||
override
|
||||
{
|
||||
checkedDigestAlgorithm = true;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA endEntityOrCA,
|
||||
unsigned int modulusSizeInBits)
|
||||
override
|
||||
{
|
||||
EXPECT_EQ(EndEntityOrCA::MustBeEndEntity, endEntityOrCA);
|
||||
EXPECT_EQ(publicKeySizeInBits, modulusSizeInBits);
|
||||
checkedModulusSizeInBits = true;
|
||||
return Success;
|
||||
}
|
||||
|
||||
const unsigned int publicKeySizeInBits;
|
||||
bool checkedDigestAlgorithm;
|
||||
bool checkedModulusSizeInBits;
|
||||
};
|
||||
|
||||
TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm)
|
||||
{
|
||||
const Time now(Now());
|
||||
const CheckSignatureAlgorithmTestParams& params(GetParam());
|
||||
|
||||
Input signatureValueInput;
|
||||
ASSERT_EQ(Success,
|
||||
signatureValueInput.Init(params.signatureValue.data(),
|
||||
params.signatureValue.length()));
|
||||
|
||||
pkixcheck_CheckSignatureAlgorithm_TrustDomain
|
||||
trustDomain(params.signatureLengthInBytes * 8);
|
||||
|
||||
der::SignedDataWithSignature signedData;
|
||||
ASSERT_EQ(Success,
|
||||
signedData.algorithm.Init(params.signatureAlgorithmValue.data(),
|
||||
params.signatureAlgorithmValue.length()));
|
||||
|
||||
ByteString dummySignature(params.signatureLengthInBytes, 0xDE);
|
||||
ASSERT_EQ(Success,
|
||||
signedData.signature.Init(dummySignature.data(),
|
||||
dummySignature.length()));
|
||||
|
||||
ASSERT_EQ(params.expectedResult,
|
||||
CheckSignatureAlgorithm(trustDomain, EndEntityOrCA::MustBeEndEntity,
|
||||
now, signedData, signatureValueInput));
|
||||
ASSERT_EQ(params.expectedResult == Success,
|
||||
trustDomain.checkedDigestAlgorithm);
|
||||
ASSERT_EQ(params.expectedResult == Success,
|
||||
trustDomain.checkedModulusSizeInBits);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
pkixcheck_CheckSignatureAlgorithm, pkixcheck_CheckSignatureAlgorithm,
|
||||
testing::ValuesIn(CHECKSIGNATUREALGORITHM_TEST_PARAMS));
|
||||
|
||||
class pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
|
||||
: public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain(
|
||||
const ByteString& aIssuer)
|
||||
: issuer(aIssuer)
|
||||
{
|
||||
}
|
||||
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
|
||||
Input cert, /*out*/ TrustLevel& trustLevel) override
|
||||
{
|
||||
trustLevel = InputEqualsByteString(cert, issuer)
|
||||
? TrustLevel::TrustAnchor
|
||||
: TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker& checker, Time) override
|
||||
{
|
||||
EXPECT_FALSE(ENCODING_FAILED(issuer));
|
||||
|
||||
Input issuerInput;
|
||||
EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length()));
|
||||
|
||||
bool keepGoing;
|
||||
EXPECT_EQ(Success, checker.Check(issuerInput, nullptr, keepGoing));
|
||||
EXPECT_FALSE(keepGoing);
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*,
|
||||
/*optional*/ const Input*) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
ByteString issuer;
|
||||
};
|
||||
|
||||
// Test that CheckSignatureAlgorithm actually gets called at some point when
|
||||
// BuildCertChain is called.
|
||||
TEST_F(pkixcheck_CheckSignatureAlgorithm, BuildCertChain)
|
||||
{
|
||||
ScopedTestKeyPair keyPair(CloneReusedKeyPair());
|
||||
ASSERT_TRUE(keyPair.get());
|
||||
|
||||
ByteString issuerExtensions[2];
|
||||
issuerExtensions[0] = CreateEncodedBasicConstraints(true, nullptr,
|
||||
Critical::No);
|
||||
ASSERT_FALSE(ENCODING_FAILED(issuerExtensions[0]));
|
||||
|
||||
ByteString issuer(CreateEncodedCertificate(3,
|
||||
sha256WithRSAEncryption(),
|
||||
CreateEncodedSerialNumber(1),
|
||||
CNToDERName("issuer"),
|
||||
oneDayBeforeNow, oneDayAfterNow,
|
||||
CNToDERName("issuer"),
|
||||
*keyPair,
|
||||
issuerExtensions,
|
||||
*keyPair,
|
||||
sha256WithRSAEncryption()));
|
||||
ASSERT_FALSE(ENCODING_FAILED(issuer));
|
||||
|
||||
ByteString subject(CreateEncodedCertificate(3,
|
||||
sha1WithRSAEncryption(),
|
||||
CreateEncodedSerialNumber(2),
|
||||
CNToDERName("issuer"),
|
||||
oneDayBeforeNow, oneDayAfterNow,
|
||||
CNToDERName("subject"),
|
||||
*keyPair,
|
||||
nullptr,
|
||||
*keyPair,
|
||||
sha256WithRSAEncryption()));
|
||||
ASSERT_FALSE(ENCODING_FAILED(subject));
|
||||
|
||||
Input subjectInput;
|
||||
ASSERT_EQ(Success, subjectInput.Init(subject.data(), subject.length()));
|
||||
pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
|
||||
trustDomain(issuer);
|
||||
Result rv = BuildCertChain(trustDomain, subjectInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr);
|
||||
ASSERT_EQ(Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, rv);
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2014 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixcheck.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
static const Time PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56));
|
||||
|
||||
#define OLDER_GENERALIZEDTIME \
|
||||
0x18, 15, /* tag, length */ \
|
||||
'1', '9', '9', '9', '0', '1', '0', '1', /* 1999-01-01 */ \
|
||||
'0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
|
||||
|
||||
#define OLDER_UTCTIME \
|
||||
0x17, 13, /* tag, length */ \
|
||||
'9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \
|
||||
'0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
|
||||
|
||||
static const Time NOW(YMDHMS(2016, 12, 31, 12, 23, 56));
|
||||
|
||||
#define NEWER_GENERALIZEDTIME \
|
||||
0x18, 15, /* tag, length */ \
|
||||
'2', '0', '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
|
||||
'0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
|
||||
|
||||
#define NEWER_UTCTIME \
|
||||
0x17, 13, /* tag, length */ \
|
||||
'2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
|
||||
'0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
|
||||
|
||||
static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
|
||||
|
||||
class pkixcheck_CheckValidity : public ::testing::Test { };
|
||||
|
||||
static const uint8_t OLDER_UTCTIME_NEWER_UTCTIME_DATA[] = {
|
||||
OLDER_UTCTIME,
|
||||
NEWER_UTCTIME,
|
||||
};
|
||||
static const Input
|
||||
OLDER_UTCTIME_NEWER_UTCTIME(OLDER_UTCTIME_NEWER_UTCTIME_DATA);
|
||||
|
||||
TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_UTCTIME)
|
||||
{
|
||||
static Time notBefore(Time::uninitialized);
|
||||
static Time notAfter(Time::uninitialized);
|
||||
ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, ¬Before, ¬After));
|
||||
ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckValidity, Valid_GENERALIZEDTIME_GENERALIZEDTIME)
|
||||
{
|
||||
static const uint8_t DER[] = {
|
||||
OLDER_GENERALIZEDTIME,
|
||||
NEWER_GENERALIZEDTIME,
|
||||
};
|
||||
static const Input validity(DER);
|
||||
static Time notBefore(Time::uninitialized);
|
||||
static Time notAfter(Time::uninitialized);
|
||||
ASSERT_EQ(Success, ParseValidity(validity, ¬Before, ¬After));
|
||||
ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckValidity, Valid_GENERALIZEDTIME_UTCTIME)
|
||||
{
|
||||
static const uint8_t DER[] = {
|
||||
OLDER_GENERALIZEDTIME,
|
||||
NEWER_UTCTIME,
|
||||
};
|
||||
static const Input validity(DER);
|
||||
static Time notBefore(Time::uninitialized);
|
||||
static Time notAfter(Time::uninitialized);
|
||||
ASSERT_EQ(Success, ParseValidity(validity, ¬Before, ¬After));
|
||||
ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_GENERALIZEDTIME)
|
||||
{
|
||||
static const uint8_t DER[] = {
|
||||
OLDER_UTCTIME,
|
||||
NEWER_GENERALIZEDTIME,
|
||||
};
|
||||
static const Input validity(DER);
|
||||
static Time notBefore(Time::uninitialized);
|
||||
static Time notAfter(Time::uninitialized);
|
||||
ASSERT_EQ(Success, ParseValidity(validity, ¬Before, ¬After));
|
||||
ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckValidity, InvalidBeforeNotBefore)
|
||||
{
|
||||
static Time notBefore(Time::uninitialized);
|
||||
static Time notAfter(Time::uninitialized);
|
||||
ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, ¬Before, ¬After));
|
||||
ASSERT_EQ(Result::ERROR_NOT_YET_VALID_CERTIFICATE, CheckValidity(PAST_TIME, notBefore, notAfter));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_CheckValidity, InvalidAfterNotAfter)
|
||||
{
|
||||
static Time notBefore(Time::uninitialized);
|
||||
static Time notAfter(Time::uninitialized);
|
||||
ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, ¬Before, ¬After));
|
||||
ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE, CheckValidity(FUTURE_TIME, notBefore, notAfter));
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2014 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixcheck.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
#define OLDER_UTCTIME \
|
||||
0x17, 13, /* tag, length */ \
|
||||
'9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \
|
||||
'0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
|
||||
|
||||
#define NEWER_UTCTIME \
|
||||
0x17, 13, /* tag, length */ \
|
||||
'2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
|
||||
'0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
|
||||
|
||||
static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
|
||||
|
||||
class pkixcheck_ParseValidity : public ::testing::Test { };
|
||||
|
||||
TEST_F(pkixcheck_ParseValidity, BothEmptyNull)
|
||||
{
|
||||
static const uint8_t DER[] = {
|
||||
0x17/*UTCTime*/, 0/*length*/,
|
||||
0x17/*UTCTime*/, 0/*length*/,
|
||||
};
|
||||
static const Input validity(DER);
|
||||
ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_ParseValidity, NotBeforeEmptyNull)
|
||||
{
|
||||
static const uint8_t DER[] = {
|
||||
0x17/*UTCTime*/, 0x00/*length*/,
|
||||
NEWER_UTCTIME
|
||||
};
|
||||
static const Input validity(DER);
|
||||
ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_ParseValidity, NotAfterEmptyNull)
|
||||
{
|
||||
static const uint8_t DER[] = {
|
||||
NEWER_UTCTIME,
|
||||
0x17/*UTCTime*/, 0x00/*length*/,
|
||||
};
|
||||
static const Input validity(DER);
|
||||
ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
|
||||
}
|
||||
|
||||
TEST_F(pkixcheck_ParseValidity, InvalidNotAfterBeforeNotBefore)
|
||||
{
|
||||
static const uint8_t DER[] = {
|
||||
NEWER_UTCTIME,
|
||||
OLDER_UTCTIME,
|
||||
};
|
||||
static const Input validity(DER);
|
||||
ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2015 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
extern Result TLSFeaturesSatisfiedInternal(const Input* requiredTLSFeatures,
|
||||
const Input* stapledOCSPResponse);
|
||||
} } // namespace mozilla::pkix
|
||||
|
||||
struct TLSFeaturesTestParams
|
||||
{
|
||||
ByteString requiredTLSFeatures;
|
||||
Result expectedResultWithResponse;
|
||||
Result expectedResultWithoutResponse;
|
||||
};
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& os, const TLSFeaturesTestParams&)
|
||||
{
|
||||
return os << "TODO (bug 1318770)";
|
||||
}
|
||||
|
||||
#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s))
|
||||
static const uint8_t statusRequest[] = {
|
||||
0x30, 0x03, 0x02, 0x01, 0x05
|
||||
};
|
||||
|
||||
static const uint8_t unknown[] = {
|
||||
0x30, 0x03, 0x02, 0x01, 0x06
|
||||
};
|
||||
|
||||
static const uint8_t statusRequestAndUnknown[] = {
|
||||
0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x06
|
||||
};
|
||||
|
||||
static const uint8_t duplicateStatusRequest[] = {
|
||||
0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x05
|
||||
};
|
||||
|
||||
static const uint8_t twoByteUnknown[] = {
|
||||
0x30, 0x04, 0x02, 0x02, 0x05, 0x05
|
||||
};
|
||||
|
||||
static const uint8_t zeroByteInteger[] = {
|
||||
0x30, 0x02, 0x02, 0x00
|
||||
};
|
||||
|
||||
static const TLSFeaturesTestParams
|
||||
TLSFEATURESSATISFIED_TEST_PARAMS[] =
|
||||
{
|
||||
// some tests with checks enforced
|
||||
{ ByteString(), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER },
|
||||
{ BS(statusRequest), Success, Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
|
||||
{ BS(unknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
|
||||
Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
|
||||
{ BS(statusRequestAndUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
|
||||
Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
|
||||
{ BS(duplicateStatusRequest), Success,
|
||||
Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
|
||||
{ BS(twoByteUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
|
||||
Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
|
||||
{ BS(zeroByteInteger), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
|
||||
Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
|
||||
};
|
||||
|
||||
class pkixcheck_TLSFeaturesSatisfiedInternal
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<TLSFeaturesTestParams>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(pkixcheck_TLSFeaturesSatisfiedInternal, TLSFeaturesSatisfiedInternal) {
|
||||
const TLSFeaturesTestParams& params(GetParam());
|
||||
|
||||
Input featuresInput;
|
||||
ASSERT_EQ(Success, featuresInput.Init(params.requiredTLSFeatures.data(),
|
||||
params.requiredTLSFeatures.length()));
|
||||
Input responseInput;
|
||||
// just create an input with any data in it
|
||||
ByteString stapledOCSPResponse = BS(statusRequest);
|
||||
ASSERT_EQ(Success, responseInput.Init(stapledOCSPResponse.data(),
|
||||
stapledOCSPResponse.length()));
|
||||
// first we omit the response
|
||||
ASSERT_EQ(params.expectedResultWithoutResponse,
|
||||
TLSFeaturesSatisfiedInternal(&featuresInput, nullptr));
|
||||
// then we try again with the response
|
||||
ASSERT_EQ(params.expectedResultWithResponse,
|
||||
TLSFeaturesSatisfiedInternal(&featuresInput, &responseInput));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
pkixcheck_TLSFeaturesSatisfiedInternal,
|
||||
pkixcheck_TLSFeaturesSatisfiedInternal,
|
||||
testing::ValuesIn(TLSFEATURESSATISFIED_TEST_PARAMS));
|
|
@ -0,0 +1,920 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 <functional>
|
||||
#include <vector>
|
||||
#include "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::der;
|
||||
|
||||
namespace {
|
||||
|
||||
class pkixder_input_tests : public ::testing::Test { };
|
||||
|
||||
static const uint8_t DER_SEQUENCE_EMPTY[] = {
|
||||
0x30, // SEQUENCE
|
||||
0x00, // length
|
||||
};
|
||||
|
||||
static const uint8_t DER_SEQUENCE_NOT_EMPTY[] = {
|
||||
0x30, // SEQUENCE
|
||||
0x01, // length
|
||||
'X', // value
|
||||
};
|
||||
|
||||
static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE[] = {
|
||||
'X', // value
|
||||
};
|
||||
|
||||
static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED[] = {
|
||||
0x30, // SEQUENCE
|
||||
0x01, // length
|
||||
};
|
||||
|
||||
const uint8_t DER_SEQUENCE_OF_INT8[] = {
|
||||
0x30, // SEQUENCE
|
||||
0x09, // length
|
||||
0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
|
||||
0x02, 0x01, 0x02, // INTEGER length 1 value 0x02
|
||||
0x02, 0x01, 0x03 // INTEGER length 1 value 0x03
|
||||
};
|
||||
|
||||
const uint8_t DER_TRUNCATED_SEQUENCE_OF_INT8[] = {
|
||||
0x30, // SEQUENCE
|
||||
0x09, // length
|
||||
0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
|
||||
0x02, 0x01, 0x02 // INTEGER length 1 value 0x02
|
||||
// MISSING DATA HERE ON PURPOSE
|
||||
};
|
||||
|
||||
const uint8_t DER_OVERRUN_SEQUENCE_OF_INT8[] = {
|
||||
0x30, // SEQUENCE
|
||||
0x09, // length
|
||||
0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
|
||||
0x02, 0x01, 0x02, // INTEGER length 1 value 0x02
|
||||
0x02, 0x02, 0xFF, 0x03 // INTEGER length 2 value 0xFF03
|
||||
};
|
||||
|
||||
const uint8_t DER_INT16[] = {
|
||||
0x02, // INTEGER
|
||||
0x02, // length
|
||||
0x12, 0x34 // 0x1234
|
||||
};
|
||||
|
||||
static const Input EMPTY_INPUT;
|
||||
|
||||
TEST_F(pkixder_input_tests, InputInit)
|
||||
{
|
||||
Input buf;
|
||||
ASSERT_EQ(Success,
|
||||
buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, InputInitWithNullPointerOrZeroLength)
|
||||
{
|
||||
Input buf;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 0));
|
||||
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 100));
|
||||
|
||||
// Though it seems odd to initialize with zero-length and non-null ptr, this
|
||||
// is working as intended. The Reader class was intended to protect against
|
||||
// buffer overflows, and there's no risk with the current behavior. See bug
|
||||
// 1000354.
|
||||
ASSERT_EQ(Success, buf.Init((const uint8_t*) "hello", 0));
|
||||
ASSERT_TRUE(buf.GetLength() == 0);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, InputInitWithLargeData)
|
||||
{
|
||||
Input buf;
|
||||
// Data argument length does not matter, it is not touched, just
|
||||
// needs to be non-null
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init((const uint8_t*) "", 0xffff+1));
|
||||
|
||||
ASSERT_EQ(Success, buf.Init((const uint8_t*) "", 0xffff));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, InputInitMultipleTimes)
|
||||
{
|
||||
Input buf;
|
||||
|
||||
ASSERT_EQ(Success,
|
||||
buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
|
||||
|
||||
ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS,
|
||||
buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, PeekWithinBounds)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x11 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
ASSERT_TRUE(input.Peek(0x11));
|
||||
ASSERT_FALSE(input.Peek(0x22));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, PeekPastBounds)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22 };
|
||||
Input buf;
|
||||
ASSERT_EQ(Success, buf.Init(der, 1));
|
||||
Reader input(buf);
|
||||
|
||||
uint8_t readByte;
|
||||
ASSERT_EQ(Success, input.Read(readByte));
|
||||
ASSERT_EQ(0x11, readByte);
|
||||
ASSERT_FALSE(input.Peek(0x22));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadByte)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
uint8_t readByte1;
|
||||
ASSERT_EQ(Success, input.Read(readByte1));
|
||||
ASSERT_EQ(0x11, readByte1);
|
||||
|
||||
uint8_t readByte2;
|
||||
ASSERT_EQ(Success, input.Read(readByte2));
|
||||
ASSERT_EQ(0x22, readByte2);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadBytePastEnd)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22 };
|
||||
Input buf;
|
||||
ASSERT_EQ(Success, buf.Init(der, 1));
|
||||
Reader input(buf);
|
||||
|
||||
uint8_t readByte1 = 0;
|
||||
ASSERT_EQ(Success, input.Read(readByte1));
|
||||
ASSERT_EQ(0x11, readByte1);
|
||||
|
||||
uint8_t readByte2 = 0;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readByte2));
|
||||
ASSERT_NE(0x22, readByte2);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadByteWrapAroundPointer)
|
||||
{
|
||||
// The original implementation of our buffer read overflow checks was
|
||||
// susceptible to integer overflows which could make the checks ineffective.
|
||||
// This attempts to verify that we've fixed that. Unfortunately, decrementing
|
||||
// a null pointer is undefined behavior according to the C++ language spec.,
|
||||
// but this should catch the problem on at least some compilers, if not all of
|
||||
// them.
|
||||
const uint8_t* der = nullptr;
|
||||
--der;
|
||||
Input buf;
|
||||
ASSERT_EQ(Success, buf.Init(der, 0));
|
||||
Reader input(buf);
|
||||
|
||||
uint8_t b;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadWord)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
uint16_t readWord1 = 0;
|
||||
ASSERT_EQ(Success, input.Read(readWord1));
|
||||
ASSERT_EQ(0x1122, readWord1);
|
||||
|
||||
uint16_t readWord2 = 0;
|
||||
ASSERT_EQ(Success, input.Read(readWord2));
|
||||
ASSERT_EQ(0x3344, readWord2);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadWordPastEnd)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf;
|
||||
ASSERT_EQ(Success, buf.Init(der, 2)); // Initialize with too-short length
|
||||
Reader input(buf);
|
||||
|
||||
uint16_t readWord1 = 0;
|
||||
ASSERT_EQ(Success, input.Read(readWord1));
|
||||
ASSERT_EQ(0x1122, readWord1);
|
||||
|
||||
uint16_t readWord2 = 0;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord2));
|
||||
ASSERT_NE(0x3344, readWord2);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadWordWithInsufficentData)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22 };
|
||||
Input buf;
|
||||
ASSERT_EQ(Success, buf.Init(der, 1));
|
||||
Reader input(buf);
|
||||
|
||||
uint16_t readWord1 = 0;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord1));
|
||||
ASSERT_NE(0x1122, readWord1);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadWordWrapAroundPointer)
|
||||
{
|
||||
// The original implementation of our buffer read overflow checks was
|
||||
// susceptible to integer overflows which could make the checks ineffective.
|
||||
// This attempts to verify that we've fixed that. Unfortunately, decrementing
|
||||
// a null pointer is undefined behavior according to the C++ language spec.,
|
||||
// but this should catch the problem on at least some compilers, if not all of
|
||||
// them.
|
||||
const uint8_t* der = nullptr;
|
||||
--der;
|
||||
Input buf;
|
||||
ASSERT_EQ(Success, buf.Init(der, 0));
|
||||
Reader input(buf);
|
||||
uint16_t b;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, Skip)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
ASSERT_EQ(Success, input.Skip(1));
|
||||
|
||||
uint8_t readByte1 = 0;
|
||||
ASSERT_EQ(Success, input.Read(readByte1));
|
||||
ASSERT_EQ(0x22, readByte1);
|
||||
|
||||
ASSERT_EQ(Success, input.Skip(1));
|
||||
|
||||
uint8_t readByte2 = 0;
|
||||
ASSERT_EQ(Success, input.Read(readByte2));
|
||||
ASSERT_EQ(0x44, readByte2);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, Skip_ToEnd)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Success, input.Skip(sizeof der));
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, Skip_PastEnd)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, Skip_ToNewInput)
|
||||
{
|
||||
const uint8_t der[] = { 0x01, 0x02, 0x03, 0x04 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
Reader skippedInput;
|
||||
ASSERT_EQ(Success, input.Skip(3, skippedInput));
|
||||
|
||||
uint8_t readByte1 = 0;
|
||||
ASSERT_EQ(Success, input.Read(readByte1));
|
||||
ASSERT_EQ(0x04, readByte1);
|
||||
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
|
||||
// Reader has no Remaining() or Length() so we simply read the bytes
|
||||
// and then expect to be at the end.
|
||||
|
||||
for (uint8_t i = 1; i <= 3; ++i) {
|
||||
uint8_t readByte = 0;
|
||||
ASSERT_EQ(Success, skippedInput.Read(readByte));
|
||||
ASSERT_EQ(i, readByte);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(skippedInput.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, Skip_ToNewInputPastEnd)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
Reader skippedInput;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der * 2, skippedInput));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, Skip_ToInput)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 };
|
||||
|
||||
Input item;
|
||||
ASSERT_EQ(Success, input.Skip(sizeof expectedItemData, item));
|
||||
|
||||
Input expected(expectedItemData);
|
||||
ASSERT_TRUE(InputsAreEqual(expected, item));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, Skip_WrapAroundPointer)
|
||||
{
|
||||
// The original implementation of our buffer read overflow checks was
|
||||
// susceptible to integer overflows which could make the checks ineffective.
|
||||
// This attempts to verify that we've fixed that. Unfortunately, decrementing
|
||||
// a null pointer is undefined behavior according to the C++ language spec.,
|
||||
// but this should catch the problem on at least some compilers, if not all of
|
||||
// them.
|
||||
const uint8_t* der = nullptr;
|
||||
--der;
|
||||
Input buf;
|
||||
ASSERT_EQ(Success, buf.Init(der, 0));
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(1));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, Skip_ToInputPastEnd)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
Input skipped;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1, skipped));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, SkipToEnd_ToInput)
|
||||
{
|
||||
static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
Input skipped;
|
||||
ASSERT_EQ(Success, input.SkipToEnd(skipped));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, SkipToEnd_ToInput_InputAlreadyInited)
|
||||
{
|
||||
static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
static const uint8_t initialValue[] = { 0x01, 0x02, 0x03 };
|
||||
Input x(initialValue);
|
||||
// Fails because skipped was already initialized once, and Inputs are not
|
||||
// allowed to be Init()d multiple times.
|
||||
ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.SkipToEnd(x));
|
||||
ASSERT_TRUE(InputsAreEqual(x, Input(initialValue)));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndSkipValue)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
|
||||
ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE));
|
||||
ASSERT_EQ(Success, End(input));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithTruncatedData)
|
||||
{
|
||||
Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndSkipValue(input, SEQUENCE));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithOverrunData)
|
||||
{
|
||||
Input buf(DER_OVERRUN_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE));
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, AtEndOnUnInitializedInput)
|
||||
{
|
||||
Reader input;
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, AtEndAtBeginning)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
ASSERT_FALSE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, AtEndAtEnd)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Success, input.Skip(sizeof der));
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, MarkAndGetInput)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
Reader::Mark mark = input.GetMark();
|
||||
|
||||
const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 };
|
||||
|
||||
ASSERT_EQ(Success, input.Skip(sizeof expectedItemData));
|
||||
|
||||
Input item;
|
||||
ASSERT_EQ(Success, input.GetInput(mark, item));
|
||||
Input expected(expectedItemData);
|
||||
ASSERT_TRUE(InputsAreEqual(expected, item));
|
||||
}
|
||||
|
||||
// Cannot run this test on debug builds because of the NotReached
|
||||
#ifdef NDEBUG
|
||||
TEST_F(pkixder_input_tests, MarkAndGetInputDifferentInput)
|
||||
{
|
||||
const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
|
||||
Reader another;
|
||||
Reader::Mark mark = another.GetMark();
|
||||
|
||||
ASSERT_EQ(Success, input.Skip(3));
|
||||
|
||||
Input item;
|
||||
ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.GetInput(mark, item));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_AtEnd)
|
||||
{
|
||||
Reader input(EMPTY_INPUT);
|
||||
uint8_t tag;
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_TruncatedAfterTag)
|
||||
{
|
||||
static const uint8_t DER[] = { SEQUENCE };
|
||||
Input buf(DER);
|
||||
Reader input(buf);
|
||||
uint8_t tag;
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_EMPTY);
|
||||
Reader input(buf);
|
||||
uint8_t tag = 0;
|
||||
Input value;
|
||||
ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value));
|
||||
ASSERT_EQ(SEQUENCE, tag);
|
||||
ASSERT_EQ(0u, value.GetLength());
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidNotEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
uint8_t tag = 0;
|
||||
Input value;
|
||||
ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value));
|
||||
ASSERT_EQ(SEQUENCE, tag);
|
||||
Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE);
|
||||
ASSERT_TRUE(InputsAreEqual(expected, value));
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests,
|
||||
ReadTagAndGetValue_Input_InvalidNotEmptyValueTruncated)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
|
||||
Reader input(buf);
|
||||
uint8_t tag;
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidWrongLength)
|
||||
{
|
||||
Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
uint8_t tag;
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ReadTagAndGetValue(input, tag, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm1)
|
||||
{
|
||||
// High tag number form is not allowed (illegal 1 byte tag)
|
||||
//
|
||||
// If the decoder treats 0x1F as a valid low tag number tag, then it will
|
||||
// treat the actual tag (1) as a length, and then it will return Success
|
||||
// with value == { 0x00 } and tag == 0x1f.
|
||||
//
|
||||
// It is illegal to encode tag 1 in the high tag number form because it isn't
|
||||
// the shortest encoding (the low tag number form is).
|
||||
static const uint8_t DER[] = {
|
||||
0x1F, // high tag number form indicator
|
||||
1, // tag 1 (not legal!)
|
||||
0 // length zero
|
||||
};
|
||||
Input buf(DER);
|
||||
Reader input(buf);
|
||||
uint8_t tag;
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ReadTagAndGetValue(input, tag, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm2)
|
||||
{
|
||||
// High tag number form is not allowed (legal 1 byte tag).
|
||||
//
|
||||
// ReadTagAndGetValue's check to prohibit the high tag number form has no
|
||||
// effect on whether this test passes or fails, because ReadTagAndGetValue
|
||||
// will interpret the second byte (31) as a length, and the input doesn't
|
||||
// have 31 bytes following it. This test is here to guard against the case
|
||||
// where somebody actually implements high tag number form parsing, to remind
|
||||
// that person that they need to add tests here, including in particular
|
||||
// tests for overly-long encodings.
|
||||
static const uint8_t DER[] = {
|
||||
0x1F, // high tag number form indicator
|
||||
31, // tag 31
|
||||
0 // length zero
|
||||
};
|
||||
Input buf(DER);
|
||||
Reader input(buf);
|
||||
uint8_t tag;
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ReadTagAndGetValue(input, tag, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm3)
|
||||
{
|
||||
// High tag number form is not allowed (2 byte legal tag)
|
||||
//
|
||||
// ReadTagAndGetValue's check to prohibit the high tag number form has no
|
||||
// effect on whether this test passes or fails, because ReadTagAndGetValue
|
||||
// will interpret the second byte as a length, and the input doesn't have
|
||||
// that many bytes following it. This test is here to guard against the case
|
||||
// where somebody actually implements high tag number form parsing, to remind
|
||||
// that person that they need to add tests here, including in particular
|
||||
// tests for overly-long encodings.
|
||||
static const uint8_t DER[] = {
|
||||
0x1F, // high tag number form indicator
|
||||
0x80 | 0x01, 0x00, // tag 0x100 (256)
|
||||
0 // length zero
|
||||
};
|
||||
Input buf(DER);
|
||||
Reader input(buf);
|
||||
uint8_t tag;
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ReadTagAndGetValue(input, tag, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_EMPTY);
|
||||
Reader input(buf);
|
||||
Reader value;
|
||||
ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
|
||||
ASSERT_TRUE(value.AtEnd());
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidNotEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
Reader value;
|
||||
ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
|
||||
ASSERT_TRUE(value.MatchRest(DER_SEQUENCE_NOT_EMPTY_VALUE));
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests,
|
||||
ExpectTagAndGetValue_Reader_InvalidNotEmptyValueTruncated)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
|
||||
Reader input(buf);
|
||||
Reader value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ExpectTagAndGetValue(input, SEQUENCE, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongLength)
|
||||
{
|
||||
Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
Reader value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ExpectTagAndGetValue(input, SEQUENCE, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongTag)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
Reader value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ExpectTagAndGetValue(input, INTEGER, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_EMPTY);
|
||||
Reader input(buf);
|
||||
Input value;
|
||||
ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
|
||||
ASSERT_EQ(0u, value.GetLength());
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidNotEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
Input value;
|
||||
ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
|
||||
Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE);
|
||||
ASSERT_TRUE(InputsAreEqual(expected, value));
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests,
|
||||
ExpectTagAndGetValue_Input_InvalidNotEmptyValueTruncated)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
|
||||
Reader input(buf);
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ExpectTagAndGetValue(input, SEQUENCE, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongLength)
|
||||
{
|
||||
Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ExpectTagAndGetValue(input, SEQUENCE, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongTag)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
Input value;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
ExpectTagAndGetValue(input, INTEGER, value));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_ValidEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_EMPTY);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Success, ExpectTagAndEmptyValue(input, SEQUENCE));
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InValidNotEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests,
|
||||
ExpectTagAndEmptyValue_Input_InvalidNotEmptyValueTruncated)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongLength)
|
||||
{
|
||||
Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongTag)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, INTEGER));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_EMPTY);
|
||||
Reader input(buf);
|
||||
Input tlv;
|
||||
ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
|
||||
Input expected(DER_SEQUENCE_EMPTY);
|
||||
ASSERT_TRUE(InputsAreEqual(expected, tlv));
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidNotEmpty)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
Input tlv;
|
||||
ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
|
||||
Input expected(DER_SEQUENCE_NOT_EMPTY);
|
||||
ASSERT_TRUE(InputsAreEqual(expected, tlv));
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests,
|
||||
ExpectTagAndGetTLV_Input_InvalidNotEmptyValueTruncated)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
|
||||
Reader input(buf);
|
||||
Input tlv;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongLength)
|
||||
{
|
||||
Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
Input tlv;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongTag)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_NOT_EMPTY);
|
||||
Reader input(buf);
|
||||
Input tlv;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, INTEGER, tlv));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, EndAtEnd)
|
||||
{
|
||||
Input buf(DER_INT16);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Success, input.Skip(4));
|
||||
ASSERT_EQ(Success, End(input));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, EndBeforeEnd)
|
||||
{
|
||||
Input buf(DER_INT16);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Success, input.Skip(2));
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, EndAtBeginning)
|
||||
{
|
||||
Input buf(DER_INT16);
|
||||
Reader input(buf);
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
|
||||
}
|
||||
|
||||
// TODO: Need tests for Nested too?
|
||||
|
||||
Result NestedOfHelper(Reader& input, std::vector<uint8_t>& readValues)
|
||||
{
|
||||
uint8_t value = 0;
|
||||
Result rv = input.Read(value);
|
||||
EXPECT_EQ(Success, rv);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
readValues.push_back(value);
|
||||
return Success;
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, NestedOf)
|
||||
{
|
||||
Input buf(DER_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
|
||||
std::vector<uint8_t> readValues;
|
||||
ASSERT_EQ(Success,
|
||||
NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No,
|
||||
[&readValues](Reader& r) {
|
||||
return NestedOfHelper(r, readValues);
|
||||
}));
|
||||
ASSERT_EQ(3u, readValues.size());
|
||||
ASSERT_EQ(0x01, readValues[0]);
|
||||
ASSERT_EQ(0x02, readValues[1]);
|
||||
ASSERT_EQ(0x03, readValues[2]);
|
||||
ASSERT_EQ(Success, End(input));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, NestedOfWithTruncatedData)
|
||||
{
|
||||
Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
|
||||
Reader input(buf);
|
||||
|
||||
std::vector<uint8_t> readValues;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No,
|
||||
[&readValues](Reader& r) {
|
||||
return NestedOfHelper(r, readValues);
|
||||
}));
|
||||
ASSERT_EQ(0u, readValues.size());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, MatchRestAtEnd)
|
||||
{
|
||||
static const uint8_t der[1] = { };
|
||||
Input buf;
|
||||
ASSERT_EQ(Success, buf.Init(der, 0));
|
||||
Reader input(buf);
|
||||
ASSERT_TRUE(input.AtEnd());
|
||||
static const uint8_t toMatch[] = { 1 };
|
||||
ASSERT_FALSE(input.MatchRest(toMatch));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, MatchRest1Match)
|
||||
{
|
||||
static const uint8_t der[] = { 1 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
ASSERT_FALSE(input.AtEnd());
|
||||
ASSERT_TRUE(input.MatchRest(der));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, MatchRest1Mismatch)
|
||||
{
|
||||
static const uint8_t der[] = { 1 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
static const uint8_t toMatch[] = { 2 };
|
||||
ASSERT_FALSE(input.MatchRest(toMatch));
|
||||
ASSERT_FALSE(input.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, MatchRest2WithTrailingByte)
|
||||
{
|
||||
static const uint8_t der[] = { 1, 2, 3 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
static const uint8_t toMatch[] = { 1, 2 };
|
||||
ASSERT_FALSE(input.MatchRest(toMatch));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_input_tests, MatchRest2Mismatch)
|
||||
{
|
||||
static const uint8_t der[] = { 1, 2, 3 };
|
||||
Input buf(der);
|
||||
Reader input(buf);
|
||||
static const uint8_t toMatchMismatch[] = { 1, 3 };
|
||||
ASSERT_FALSE(input.MatchRest(toMatchMismatch));
|
||||
ASSERT_TRUE(input.MatchRest(der));
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -0,0 +1,480 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixtypes.h"
|
||||
#include "mozpkix/pkixder.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::der;
|
||||
|
||||
class pkixder_pki_types_tests : public ::testing::Test { };
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, CertificateSerialNumber)
|
||||
{
|
||||
const uint8_t DER_CERT_SERIAL[] = {
|
||||
0x02, // INTEGER
|
||||
8, // length
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
|
||||
};
|
||||
Input input(DER_CERT_SERIAL);
|
||||
Reader reader(input);
|
||||
|
||||
Input item;
|
||||
ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
|
||||
|
||||
Input expected;
|
||||
ASSERT_EQ(Success,
|
||||
expected.Init(DER_CERT_SERIAL + 2, sizeof DER_CERT_SERIAL - 2));
|
||||
ASSERT_TRUE(InputsAreEqual(expected, item));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, CertificateSerialNumberLongest)
|
||||
{
|
||||
const uint8_t DER_CERT_SERIAL_LONGEST[] = {
|
||||
0x02, // INTEGER
|
||||
20, // length
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
|
||||
};
|
||||
Input input(DER_CERT_SERIAL_LONGEST);
|
||||
Reader reader(input);
|
||||
|
||||
Input item;
|
||||
ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
|
||||
|
||||
Input expected;
|
||||
ASSERT_EQ(Success,
|
||||
expected.Init(DER_CERT_SERIAL_LONGEST + 2,
|
||||
sizeof DER_CERT_SERIAL_LONGEST - 2));
|
||||
ASSERT_TRUE(InputsAreEqual(expected, item));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, CertificateSerialNumberCrazyLong)
|
||||
{
|
||||
const uint8_t DER_CERT_SERIAL_CRAZY_LONG[] = {
|
||||
0x02, // INTEGER
|
||||
32, // length
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
|
||||
};
|
||||
Input input(DER_CERT_SERIAL_CRAZY_LONG);
|
||||
Reader reader(input);
|
||||
|
||||
Input item;
|
||||
ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, CertificateSerialNumberZeroLength)
|
||||
{
|
||||
const uint8_t DER_CERT_SERIAL_ZERO_LENGTH[] = {
|
||||
0x02, // INTEGER
|
||||
0x00 // length
|
||||
};
|
||||
Input input(DER_CERT_SERIAL_ZERO_LENGTH);
|
||||
Reader reader(input);
|
||||
|
||||
Input item;
|
||||
ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING,
|
||||
CertificateSerialNumber(reader, item));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, OptionalVersionV1ExplicitEncodingAllowed)
|
||||
{
|
||||
const uint8_t DER_OPTIONAL_VERSION_V1[] = {
|
||||
0xa0, 0x03, // context specific 0
|
||||
0x02, 0x01, 0x00 // INTEGER(0)
|
||||
};
|
||||
Input input(DER_OPTIONAL_VERSION_V1);
|
||||
Reader reader(input);
|
||||
|
||||
// XXX(bug 1031093): We shouldn't accept an explicit encoding of v1, but we
|
||||
// do here for compatibility reasons.
|
||||
// Version version;
|
||||
// ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
|
||||
der::Version version = der::Version::v3;
|
||||
ASSERT_EQ(Success, OptionalVersion(reader, version));
|
||||
ASSERT_EQ(der::Version::v1, version);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, OptionalVersionV2)
|
||||
{
|
||||
const uint8_t DER_OPTIONAL_VERSION_V2[] = {
|
||||
0xa0, 0x03, // context specific 0
|
||||
0x02, 0x01, 0x01 // INTEGER(1)
|
||||
};
|
||||
Input input(DER_OPTIONAL_VERSION_V2);
|
||||
Reader reader(input);
|
||||
|
||||
der::Version version = der::Version::v1;
|
||||
ASSERT_EQ(Success, OptionalVersion(reader, version));
|
||||
ASSERT_EQ(der::Version::v2, version);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, OptionalVersionV3)
|
||||
{
|
||||
const uint8_t DER_OPTIONAL_VERSION_V3[] = {
|
||||
0xa0, 0x03, // context specific 0
|
||||
0x02, 0x01, 0x02 // INTEGER(2)
|
||||
};
|
||||
Input input(DER_OPTIONAL_VERSION_V3);
|
||||
Reader reader(input);
|
||||
|
||||
der::Version version = der::Version::v1;
|
||||
ASSERT_EQ(Success, OptionalVersion(reader, version));
|
||||
ASSERT_EQ(der::Version::v3, version);
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, OptionalVersionUnknown)
|
||||
{
|
||||
const uint8_t DER_OPTIONAL_VERSION_INVALID[] = {
|
||||
0xa0, 0x03, // context specific 0
|
||||
0x02, 0x01, 0x42 // INTEGER(0x42)
|
||||
};
|
||||
Input input(DER_OPTIONAL_VERSION_INVALID);
|
||||
Reader reader(input);
|
||||
|
||||
der::Version version = der::Version::v1;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, OptionalVersionInvalidTooLong)
|
||||
{
|
||||
const uint8_t DER_OPTIONAL_VERSION_INVALID_TOO_LONG[] = {
|
||||
0xa0, 0x03, // context specific 0
|
||||
0x02, 0x02, 0x12, 0x34 // INTEGER(0x1234)
|
||||
};
|
||||
Input input(DER_OPTIONAL_VERSION_INVALID_TOO_LONG);
|
||||
Reader reader(input);
|
||||
|
||||
der::Version version;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
|
||||
}
|
||||
|
||||
TEST_F(pkixder_pki_types_tests, OptionalVersionMissing)
|
||||
{
|
||||
const uint8_t DER_OPTIONAL_VERSION_MISSING[] = {
|
||||
0x02, 0x11, 0x22 // INTEGER
|
||||
};
|
||||
Input input(DER_OPTIONAL_VERSION_MISSING);
|
||||
Reader reader(input);
|
||||
|
||||
der::Version version = der::Version::v3;
|
||||
ASSERT_EQ(Success, OptionalVersion(reader, version));
|
||||
ASSERT_EQ(der::Version::v1, version);
|
||||
}
|
||||
|
||||
static const size_t MAX_ALGORITHM_OID_DER_LENGTH = 13;
|
||||
|
||||
struct InvalidAlgorithmIdentifierTestInfo
|
||||
{
|
||||
uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
|
||||
size_t derLength;
|
||||
};
|
||||
|
||||
struct ValidDigestAlgorithmIdentifierTestInfo
|
||||
{
|
||||
DigestAlgorithm algorithm;
|
||||
uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
|
||||
size_t derLength;
|
||||
};
|
||||
|
||||
class pkixder_DigestAlgorithmIdentifier_Valid
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<ValidDigestAlgorithmIdentifierTestInfo>
|
||||
{
|
||||
};
|
||||
|
||||
static const ValidDigestAlgorithmIdentifierTestInfo
|
||||
VALID_DIGEST_ALGORITHM_TEST_INFO[] =
|
||||
{
|
||||
{ DigestAlgorithm::sha512,
|
||||
{ 0x30, 0x0b, 0x06, 0x09,
|
||||
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 },
|
||||
13
|
||||
},
|
||||
{ DigestAlgorithm::sha384,
|
||||
{ 0x30, 0x0b, 0x06, 0x09,
|
||||
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 },
|
||||
13
|
||||
},
|
||||
{ DigestAlgorithm::sha256,
|
||||
{ 0x30, 0x0b, 0x06, 0x09,
|
||||
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 },
|
||||
13
|
||||
},
|
||||
{ DigestAlgorithm::sha1,
|
||||
{ 0x30, 0x07, 0x06, 0x05,
|
||||
0x2b, 0x0e, 0x03, 0x02, 0x1a },
|
||||
9
|
||||
},
|
||||
};
|
||||
|
||||
TEST_P(pkixder_DigestAlgorithmIdentifier_Valid, Valid)
|
||||
{
|
||||
const ValidDigestAlgorithmIdentifierTestInfo& param(GetParam());
|
||||
|
||||
{
|
||||
Input input;
|
||||
ASSERT_EQ(Success, input.Init(param.der, param.derLength));
|
||||
Reader reader(input);
|
||||
DigestAlgorithm alg;
|
||||
ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg));
|
||||
ASSERT_EQ(param.algorithm, alg);
|
||||
ASSERT_EQ(Success, End(reader));
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2];
|
||||
memcpy(derWithNullParam, param.der, param.derLength);
|
||||
derWithNullParam[1] += 2; // we're going to expand the value by 2 bytes
|
||||
derWithNullParam[param.derLength] = 0x05; // NULL tag
|
||||
derWithNullParam[param.derLength + 1] = 0x00; // length zero
|
||||
|
||||
Input input;
|
||||
ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2));
|
||||
Reader reader(input);
|
||||
DigestAlgorithm alg;
|
||||
ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg));
|
||||
ASSERT_EQ(param.algorithm, alg);
|
||||
ASSERT_EQ(Success, End(reader));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Valid,
|
||||
pkixder_DigestAlgorithmIdentifier_Valid,
|
||||
testing::ValuesIn(VALID_DIGEST_ALGORITHM_TEST_INFO));
|
||||
|
||||
class pkixder_DigestAlgorithmIdentifier_Invalid
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo>
|
||||
{
|
||||
};
|
||||
|
||||
static const InvalidAlgorithmIdentifierTestInfo
|
||||
INVALID_DIGEST_ALGORITHM_TEST_INFO[] =
|
||||
{
|
||||
{ // MD5
|
||||
{ 0x30, 0x0a, 0x06, 0x08,
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05 },
|
||||
12,
|
||||
},
|
||||
{ // ecdsa-with-SHA256 (1.2.840.10045.4.3.2) (not a hash algorithm)
|
||||
{ 0x30, 0x0a, 0x06, 0x08,
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 },
|
||||
12,
|
||||
},
|
||||
};
|
||||
|
||||
TEST_P(pkixder_DigestAlgorithmIdentifier_Invalid, Invalid)
|
||||
{
|
||||
const InvalidAlgorithmIdentifierTestInfo& param(GetParam());
|
||||
Input input;
|
||||
ASSERT_EQ(Success, input.Init(param.der, param.derLength));
|
||||
Reader reader(input);
|
||||
DigestAlgorithm alg;
|
||||
ASSERT_EQ(Result::ERROR_INVALID_ALGORITHM,
|
||||
DigestAlgorithmIdentifier(reader, alg));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Invalid,
|
||||
pkixder_DigestAlgorithmIdentifier_Invalid,
|
||||
testing::ValuesIn(INVALID_DIGEST_ALGORITHM_TEST_INFO));
|
||||
|
||||
struct ValidSignatureAlgorithmIdentifierValueTestInfo
|
||||
{
|
||||
PublicKeyAlgorithm publicKeyAlg;
|
||||
DigestAlgorithm digestAlg;
|
||||
uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
|
||||
size_t derLength;
|
||||
};
|
||||
|
||||
static const ValidSignatureAlgorithmIdentifierValueTestInfo
|
||||
VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] =
|
||||
{
|
||||
// ECDSA
|
||||
{ PublicKeyAlgorithm::ECDSA,
|
||||
DigestAlgorithm::sha512,
|
||||
{ 0x06, 0x08,
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 },
|
||||
10,
|
||||
},
|
||||
{ PublicKeyAlgorithm::ECDSA,
|
||||
DigestAlgorithm::sha384,
|
||||
{ 0x06, 0x08,
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 },
|
||||
10,
|
||||
},
|
||||
{ PublicKeyAlgorithm::ECDSA,
|
||||
DigestAlgorithm::sha256,
|
||||
{ 0x06, 0x08,
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 },
|
||||
10,
|
||||
},
|
||||
{ PublicKeyAlgorithm::ECDSA,
|
||||
DigestAlgorithm::sha1,
|
||||
{ 0x06, 0x07,
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01 },
|
||||
9,
|
||||
},
|
||||
|
||||
// RSA
|
||||
{ PublicKeyAlgorithm::RSA_PKCS1,
|
||||
DigestAlgorithm::sha512,
|
||||
{ 0x06, 0x09,
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d },
|
||||
11,
|
||||
},
|
||||
{ PublicKeyAlgorithm::RSA_PKCS1,
|
||||
DigestAlgorithm::sha384,
|
||||
{ 0x06, 0x09,
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c },
|
||||
11,
|
||||
},
|
||||
{ PublicKeyAlgorithm::RSA_PKCS1,
|
||||
DigestAlgorithm::sha256,
|
||||
{ 0x06, 0x09,
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b },
|
||||
11,
|
||||
},
|
||||
{ PublicKeyAlgorithm::RSA_PKCS1,
|
||||
DigestAlgorithm::sha1,
|
||||
// IETF Standard OID
|
||||
{ 0x06, 0x09,
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 },
|
||||
11,
|
||||
},
|
||||
{ PublicKeyAlgorithm::RSA_PKCS1,
|
||||
DigestAlgorithm::sha1,
|
||||
// Legacy OIW OID (bug 1042479)
|
||||
{ 0x06, 0x05,
|
||||
0x2b, 0x0e, 0x03, 0x02, 0x1d },
|
||||
7,
|
||||
},
|
||||
};
|
||||
|
||||
class pkixder_SignatureAlgorithmIdentifierValue_Valid
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<
|
||||
ValidSignatureAlgorithmIdentifierValueTestInfo>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(pkixder_SignatureAlgorithmIdentifierValue_Valid, Valid)
|
||||
{
|
||||
const ValidSignatureAlgorithmIdentifierValueTestInfo& param(GetParam());
|
||||
|
||||
{
|
||||
Input input;
|
||||
ASSERT_EQ(Success, input.Init(param.der, param.derLength));
|
||||
Reader reader(input);
|
||||
PublicKeyAlgorithm publicKeyAlg;
|
||||
DigestAlgorithm digestAlg;
|
||||
ASSERT_EQ(Success,
|
||||
SignatureAlgorithmIdentifierValue(reader, publicKeyAlg,
|
||||
digestAlg));
|
||||
ASSERT_EQ(param.publicKeyAlg, publicKeyAlg);
|
||||
ASSERT_EQ(param.digestAlg, digestAlg);
|
||||
ASSERT_EQ(Success, End(reader));
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2];
|
||||
memcpy(derWithNullParam, param.der, param.derLength);
|
||||
derWithNullParam[param.derLength] = 0x05; // NULL tag
|
||||
derWithNullParam[param.derLength + 1] = 0x00; // length zero
|
||||
|
||||
Input input;
|
||||
ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2));
|
||||
Reader reader(input);
|
||||
PublicKeyAlgorithm publicKeyAlg;
|
||||
DigestAlgorithm digestAlg;
|
||||
ASSERT_EQ(Success,
|
||||
SignatureAlgorithmIdentifierValue(reader, publicKeyAlg,
|
||||
digestAlg));
|
||||
ASSERT_EQ(param.publicKeyAlg, publicKeyAlg);
|
||||
ASSERT_EQ(param.digestAlg, digestAlg);
|
||||
ASSERT_EQ(Success, End(reader));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
pkixder_SignatureAlgorithmIdentifierValue_Valid,
|
||||
pkixder_SignatureAlgorithmIdentifierValue_Valid,
|
||||
testing::ValuesIn(VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO));
|
||||
|
||||
static const InvalidAlgorithmIdentifierTestInfo
|
||||
INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] =
|
||||
{
|
||||
// id-dsa-with-sha256 (2.16.840.1.101.3.4.3.2)
|
||||
{ { 0x06, 0x09,
|
||||
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x02 },
|
||||
11,
|
||||
},
|
||||
|
||||
// id-dsa-with-sha1 (1.2.840.10040.4.3)
|
||||
{ { 0x06, 0x07,
|
||||
0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03 },
|
||||
9,
|
||||
},
|
||||
|
||||
// RSA-with-MD5 (1.2.840.113549.1.1.4)
|
||||
{ { 0x06, 0x09,
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 },
|
||||
11,
|
||||
},
|
||||
|
||||
// id-sha256 (2.16.840.1.101.3.4.2.1). It is invalid because SHA-256 is not
|
||||
// a signature algorithm.
|
||||
{ { 0x06, 0x09,
|
||||
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 },
|
||||
11,
|
||||
},
|
||||
};
|
||||
|
||||
class pkixder_SignatureAlgorithmIdentifier_Invalid
|
||||
: public ::testing::Test
|
||||
, public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(pkixder_SignatureAlgorithmIdentifier_Invalid, Invalid)
|
||||
{
|
||||
const InvalidAlgorithmIdentifierTestInfo& param(GetParam());
|
||||
Input input;
|
||||
ASSERT_EQ(Success, input.Init(param.der, param.derLength));
|
||||
Reader reader(input);
|
||||
der::PublicKeyAlgorithm publicKeyAlg;
|
||||
DigestAlgorithm digestAlg;
|
||||
ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
|
||||
SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, digestAlg));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
pkixder_SignatureAlgorithmIdentifier_Invalid,
|
||||
pkixder_SignatureAlgorithmIdentifier_Invalid,
|
||||
testing::ValuesIn(INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO));
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,46 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include "mozpkix/Time.h"
|
||||
|
||||
namespace mozilla { namespace pkix { namespace test {
|
||||
|
||||
static const std::time_t ONE_DAY_IN_SECONDS_AS_TIME_T =
|
||||
static_cast<std::time_t>(Time::ONE_DAY_IN_SECONDS);
|
||||
|
||||
// This assumes that time/time_t are POSIX-compliant in that time() returns
|
||||
// the number of seconds since the Unix epoch.
|
||||
static const std::time_t now(time(nullptr));
|
||||
const std::time_t oneDayBeforeNow(now - ONE_DAY_IN_SECONDS_AS_TIME_T);
|
||||
const std::time_t oneDayAfterNow(now + ONE_DAY_IN_SECONDS_AS_TIME_T);
|
||||
const std::time_t twoDaysBeforeNow(now - (2 * ONE_DAY_IN_SECONDS_AS_TIME_T));
|
||||
const std::time_t twoDaysAfterNow(now + (2 * ONE_DAY_IN_SECONDS_AS_TIME_T));
|
||||
const std::time_t tenDaysBeforeNow(now - (10 * ONE_DAY_IN_SECONDS_AS_TIME_T));
|
||||
const std::time_t tenDaysAfterNow(now + (10 * ONE_DAY_IN_SECONDS_AS_TIME_T));
|
||||
|
||||
} } } // namespace mozilla::pkix::test
|
|
@ -0,0 +1,229 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2014 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef mozilla_pkix_pkixgtest_h
|
||||
#define mozilla_pkix_pkixgtest_h
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
#pragma clang diagnostic ignored "-Wmissing-noreturn"
|
||||
#pragma clang diagnostic ignored "-Wshift-sign-overflow"
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
#pragma clang diagnostic ignored "-Wundef"
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wextra"
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning(push, 3)
|
||||
// C4224: Nonstandard extension used: formal parameter 'X' was previously
|
||||
// defined as a type.
|
||||
#pragma warning(disable : 4224)
|
||||
// C4826: Conversion from 'type1 ' to 'type_2' is sign - extended. This may
|
||||
// cause unexpected runtime behavior.
|
||||
#pragma warning(disable : 4826)
|
||||
#endif
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include "mozpkix/pkix.h"
|
||||
#include "mozpkix/test/pkixtestutil.h"
|
||||
|
||||
// PrintTo must be in the same namespace as the type we're overloading it for.
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
inline void PrintTo(const Result& result, ::std::ostream* os) {
|
||||
const char* stringified = MapResultToName(result);
|
||||
if (stringified) {
|
||||
*os << stringified;
|
||||
} else {
|
||||
*os << "mozilla::pkix::Result(" << static_cast<unsigned int>(result) << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
namespace test {
|
||||
|
||||
extern const std::time_t oneDayBeforeNow;
|
||||
extern const std::time_t oneDayAfterNow;
|
||||
extern const std::time_t twoDaysBeforeNow;
|
||||
extern const std::time_t twoDaysAfterNow;
|
||||
extern const std::time_t tenDaysBeforeNow;
|
||||
extern const std::time_t tenDaysAfterNow;
|
||||
|
||||
class EverythingFailsByDefaultTrustDomain : public TrustDomain {
|
||||
public:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
|
||||
/*out*/ TrustLevel&) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("GetCertTrust should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker&, Time) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("FindIssuer should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*,
|
||||
/*optional*/ const Input*) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckRevocation should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("IsChainValid should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result DigestBuf(Input, DigestAlgorithm, /*out*/ uint8_t*, size_t) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("DigestBuf should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA,
|
||||
Time) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckSignatureDigestAlgorithm should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckECDSACurveIsAcceptable should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest&, Input) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("VerifyECDSASignedDigest should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA,
|
||||
unsigned int) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckRSAPublicKeyModulusSizeInBits should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("VerifyRSAPKCS1SignedDigest should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA,
|
||||
KeyPurposeId) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckValidityIsAcceptable should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
Result NetscapeStepUpMatchesServerAuth(Time, bool&) override {
|
||||
ADD_FAILURE();
|
||||
return NotReached("NetscapeStepUpMatchesServerAuth should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
virtual void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override {
|
||||
ADD_FAILURE();
|
||||
}
|
||||
};
|
||||
|
||||
class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain {
|
||||
Result DigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestBufLen) override {
|
||||
return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA,
|
||||
Time) override {
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override {
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override {
|
||||
return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA,
|
||||
unsigned int) override {
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override {
|
||||
return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA,
|
||||
KeyPurposeId) override {
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result NetscapeStepUpMatchesServerAuth(Time, /*out*/ bool& matches) override {
|
||||
matches = true;
|
||||
return Success;
|
||||
}
|
||||
|
||||
void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override {}
|
||||
};
|
||||
|
||||
class DefaultNameMatchingPolicy : public NameMatchingPolicy {
|
||||
public:
|
||||
virtual Result FallBackToCommonName(
|
||||
Time,
|
||||
/*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) override {
|
||||
fallBackToCommonName = FallBackToSearchWithinSubject::Yes;
|
||||
return Success;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::pkix::test
|
||||
|
||||
#endif // mozilla_pkix_pkixgtest_h
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,146 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "pkixgtest.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
using namespace mozilla::pkix::test;
|
||||
|
||||
class CreateEncodedOCSPRequestTrustDomain final
|
||||
: public EverythingFailsByDefaultTrustDomain
|
||||
{
|
||||
private:
|
||||
Result DigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t *digestBuf, size_t digestBufLen)
|
||||
override
|
||||
{
|
||||
return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
};
|
||||
|
||||
class pkixocsp_CreateEncodedOCSPRequest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void MakeIssuerCertIDComponents(const char* issuerASCII,
|
||||
/*out*/ ByteString& issuerDER,
|
||||
/*out*/ ByteString& issuerSPKI)
|
||||
{
|
||||
issuerDER = CNToDERName(issuerASCII);
|
||||
ASSERT_FALSE(ENCODING_FAILED(issuerDER));
|
||||
|
||||
ScopedTestKeyPair keyPair(GenerateKeyPair());
|
||||
ASSERT_TRUE(keyPair.get());
|
||||
issuerSPKI = keyPair->subjectPublicKeyInfo;
|
||||
}
|
||||
|
||||
CreateEncodedOCSPRequestTrustDomain trustDomain;
|
||||
};
|
||||
|
||||
// Test that the large length of the child serial number causes
|
||||
// CreateEncodedOCSPRequest to fail.
|
||||
TEST_F(pkixocsp_CreateEncodedOCSPRequest, ChildCertLongSerialNumberTest)
|
||||
{
|
||||
static const uint8_t UNSUPPORTED_LEN = 128; // must be larger than 127
|
||||
|
||||
ByteString serialNumberString;
|
||||
// tag + length + value is 1 + 2 + UNSUPPORTED_LEN
|
||||
// Encoding the length takes two bytes: one byte to indicate that a
|
||||
// second byte follows, and the second byte to indicate the length.
|
||||
serialNumberString.push_back(0x80 + 1);
|
||||
serialNumberString.push_back(UNSUPPORTED_LEN);
|
||||
// value is 0x010000...00
|
||||
serialNumberString.push_back(0x01);
|
||||
for (size_t i = 1; i < UNSUPPORTED_LEN; ++i) {
|
||||
serialNumberString.push_back(0x00);
|
||||
}
|
||||
|
||||
ByteString issuerDER;
|
||||
ByteString issuerSPKI;
|
||||
ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER,
|
||||
issuerSPKI));
|
||||
|
||||
Input issuer;
|
||||
ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length()));
|
||||
|
||||
Input spki;
|
||||
ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length()));
|
||||
|
||||
Input serialNumber;
|
||||
ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(),
|
||||
serialNumberString.length()));
|
||||
|
||||
uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH];
|
||||
size_t ocspRequestLength;
|
||||
ASSERT_EQ(Result::ERROR_BAD_DER,
|
||||
CreateEncodedOCSPRequest(trustDomain,
|
||||
CertID(issuer, spki, serialNumber),
|
||||
ocspRequest, ocspRequestLength));
|
||||
}
|
||||
|
||||
// Test that CreateEncodedOCSPRequest handles the longest serial number that
|
||||
// it's required to support (i.e. 20 octets).
|
||||
TEST_F(pkixocsp_CreateEncodedOCSPRequest, LongestSupportedSerialNumberTest)
|
||||
{
|
||||
static const uint8_t LONGEST_REQUIRED_LEN = 20;
|
||||
|
||||
ByteString serialNumberString;
|
||||
// tag + length + value is 1 + 1 + LONGEST_REQUIRED_LEN
|
||||
serialNumberString.push_back(der::INTEGER);
|
||||
serialNumberString.push_back(LONGEST_REQUIRED_LEN);
|
||||
serialNumberString.push_back(0x01);
|
||||
// value is 0x010000...00
|
||||
for (size_t i = 1; i < LONGEST_REQUIRED_LEN; ++i) {
|
||||
serialNumberString.push_back(0x00);
|
||||
}
|
||||
|
||||
ByteString issuerDER;
|
||||
ByteString issuerSPKI;
|
||||
ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER,
|
||||
issuerSPKI));
|
||||
|
||||
Input issuer;
|
||||
ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length()));
|
||||
|
||||
Input spki;
|
||||
ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length()));
|
||||
|
||||
Input serialNumber;
|
||||
ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(),
|
||||
serialNumberString.length()));
|
||||
|
||||
uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH];
|
||||
size_t ocspRequestLength;
|
||||
ASSERT_EQ(Success,
|
||||
CreateEncodedOCSPRequest(trustDomain,
|
||||
CertID(issuer, spki, serialNumber),
|
||||
ocspRequest, ocspRequestLength));
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -18,7 +18,7 @@
|
|||
#include "ssl3prot.h"
|
||||
#include "sslerr.h"
|
||||
#include "sslproto.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
#include "nsskeys.h"
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "secerr.h"
|
||||
#include "sechash.h"
|
||||
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
#include "gcm-vectors.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "pk11pub.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "sechash.h"
|
||||
|
||||
#include "cpputil.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "pk11pub.h"
|
||||
|
||||
#include "cpputil.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "secutil.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "sechash.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
#include "pk11_ecdsa_vectors.h"
|
||||
#include "pk11_signature_test.h"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "prerror.h"
|
||||
#include "nss.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "cpputil.h"
|
||||
#include "databuffer.h"
|
||||
#include "util.h"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "pk11pub.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "pk11pub.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "pk11pub.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "cpputil.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "sechash.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
#include "pk11_signature_test.h"
|
||||
#include "pk11_rsapss_vectors.h"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "sechash.h"
|
||||
|
||||
#include "cpputil.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "databuffer.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "pk11pub.h"
|
||||
#include "secerr.h"
|
||||
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
#define GTEST_HAS_RTTI 0
|
||||
#include "gtest/gtest.h"
|
||||
|
|
|
@ -19,7 +19,7 @@ extern "C" {
|
|||
|
||||
#include "databuffer.h"
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -15,7 +15,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -17,7 +17,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "sslproto.h"
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -14,7 +14,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -17,7 +17,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "sslproto.h"
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "sslproto.h"
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "ssl3prot.h"
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -15,7 +15,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -18,7 +18,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "sslproto.h"
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -18,7 +18,8 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "scoped_ptrs_ssl.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -17,7 +17,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "sslproto.h"
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "sslproto.h"
|
||||
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "tls_connect.h"
|
||||
#include "tls_filter.h"
|
||||
#include "tls_parser.h"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include "databuffer.h"
|
||||
#include "dummy_io.h"
|
||||
#include "prio.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "sslt.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
|
|
@ -26,7 +26,7 @@ extern "C" {
|
|||
#define GTEST_HAS_RTTI 0
|
||||
#include "gtest/gtest.h"
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
extern std::string g_working_dir_path;
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
|
||||
#define GTEST_HAS_RTTI 0
|
||||
#include "gtest/gtest.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "scoped_ptrs_ssl.h"
|
||||
|
||||
extern bool g_ssl_gtest_verbose;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ extern "C" {
|
|||
|
||||
#include "databuffer.h"
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
#include "sslproto.h"
|
||||
|
||||
extern std::string g_working_dir_path;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#include "databuffer.h"
|
||||
#include "gtest_utils.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
namespace nss_test {
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ Usage: build.sh [-hcv] [-cc] [-j <n>] [--nspr] [--gyp|-g] [--opt|-o] [-m32]
|
|||
[--asan] [--ubsan] [--msan] [--sancov[=edge|bb|func|...]]
|
||||
[--disable-tests] [--fuzz[=tls|oss]] [--system-sqlite]
|
||||
[--no-zdefs] [--with-nspr] [--system-nspr] [--enable-libpkix]
|
||||
[--enable-fips]
|
||||
[--enable-fips] [--mozpkix-only]
|
||||
|
||||
This script builds NSS with gyp and ninja.
|
||||
|
||||
|
@ -48,3 +48,5 @@ NSS build tool options:
|
|||
might not work on all systems.
|
||||
--enable-libpkix make libpkix part of the build.
|
||||
--enable-fips don't disable FIPS checks.
|
||||
--mozpkix-only build only static mozpkix and mozpkix-test libraries.
|
||||
Note that support for this build option is limited.
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
...
|
|
@ -0,0 +1,47 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
{
|
||||
'includes': [
|
||||
'../../coreconf/config.gypi'
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'lib_mozpkix_exports',
|
||||
'type': 'none',
|
||||
'copies': [
|
||||
{
|
||||
'files': [
|
||||
'<(DEPTH)/cpputil/nss_scoped_ptrs.h',
|
||||
'include/pkix/Input.h',
|
||||
'include/pkix/Time.h',
|
||||
'include/pkix/Result.h',
|
||||
'include/pkix/pkix.h',
|
||||
'include/pkix/pkixnss.h',
|
||||
'include/pkix/pkixtypes.h',
|
||||
'include/pkix/pkixutil.h',
|
||||
'include/pkix/pkixcheck.h',
|
||||
'include/pkix/pkixder.h',
|
||||
],
|
||||
'destination': '<(nss_public_dist_dir)/<(module)/mozpkix'
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'lib_mozpkix_test_exports',
|
||||
'type': 'none',
|
||||
'copies': [
|
||||
{
|
||||
'files': [
|
||||
'include/pkix-test/pkixtestutil.h',
|
||||
'include/pkix-test/pkixtestnss.h',
|
||||
],
|
||||
'destination': '<(nss_public_dist_dir)/<(module)/mozpkix/test'
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
'variables': {
|
||||
'module': 'nss'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2018 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This file provides some implementation-specific test utilities. This is only
|
||||
// necessary because some PSM xpcshell test utilities overlap in functionality
|
||||
// with these test utilities, so the underlying implementation is shared.
|
||||
|
||||
#ifndef mozilla_pkix_test_pkixtestnss_h
|
||||
#define mozilla_pkix_test_pkixtestnss_h
|
||||
|
||||
#include <keyhi.h>
|
||||
#include <keythi.h>
|
||||
#include "mozpkix/nss_scoped_ptrs.h"
|
||||
#include "mozpkix/test/pkixtestutil.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
namespace test {
|
||||
|
||||
TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg,
|
||||
const ScopedSECKEYPublicKey& publicKey,
|
||||
const ScopedSECKEYPrivateKey& privateKey);
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::pkix::test
|
||||
|
||||
#endif // mozilla_pkix_test_pkixtestnss_h
|
|
@ -0,0 +1,406 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_test_pkixtestutil_h
|
||||
#define mozilla_pkix_test_pkixtestutil_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
#include "mozpkix/pkixtypes.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
namespace test {
|
||||
|
||||
typedef std::basic_string<uint8_t> ByteString;
|
||||
|
||||
inline bool ENCODING_FAILED(const ByteString& bs) { return bs.empty(); }
|
||||
|
||||
template <size_t L>
|
||||
inline ByteString BytesToByteString(const uint8_t (&bytes)[L]) {
|
||||
return ByteString(bytes, L);
|
||||
}
|
||||
|
||||
// XXX: Ideally, we should define this instead:
|
||||
//
|
||||
// template <typename T, std::size_t N>
|
||||
// constexpr inline std::size_t
|
||||
// ArrayLength(T (&)[N])
|
||||
// {
|
||||
// return N;
|
||||
// }
|
||||
//
|
||||
// However, we don't because not all supported compilers support constexpr,
|
||||
// and we need to calculate array lengths in static_assert sometimes.
|
||||
//
|
||||
// XXX: Evaluates its argument twice
|
||||
#define MOZILLA_PKIX_ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
bool InputEqualsByteString(Input input, const ByteString& bs);
|
||||
ByteString InputToByteString(Input input);
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-kp-OCSPSigning 1.3.6.1.5.5.7.3.9
|
||||
static const uint8_t tlv_id_kp_OCSPSigning[] = {0x06, 0x08, 0x2b, 0x06, 0x01,
|
||||
0x05, 0x05, 0x07, 0x03, 0x09};
|
||||
|
||||
// python DottedOIDToCode.py --tlv id-kp-serverAuth 1.3.6.1.5.5.7.3.1
|
||||
static const uint8_t tlv_id_kp_serverAuth[] = {0x06, 0x08, 0x2b, 0x06, 0x01,
|
||||
0x05, 0x05, 0x07, 0x03, 0x01};
|
||||
|
||||
enum class TestDigestAlgorithmID {
|
||||
MD2,
|
||||
MD5,
|
||||
SHA1,
|
||||
SHA224,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512,
|
||||
};
|
||||
|
||||
struct TestPublicKeyAlgorithm {
|
||||
explicit TestPublicKeyAlgorithm(const ByteString& aAlgorithmIdentifier)
|
||||
: algorithmIdentifier(aAlgorithmIdentifier) {}
|
||||
bool operator==(const TestPublicKeyAlgorithm& other) const {
|
||||
return algorithmIdentifier == other.algorithmIdentifier;
|
||||
}
|
||||
ByteString algorithmIdentifier;
|
||||
};
|
||||
|
||||
ByteString DSS_P();
|
||||
ByteString DSS_Q();
|
||||
ByteString DSS_G();
|
||||
|
||||
TestPublicKeyAlgorithm DSS();
|
||||
TestPublicKeyAlgorithm RSA_PKCS1();
|
||||
|
||||
struct TestSignatureAlgorithm {
|
||||
TestSignatureAlgorithm(const TestPublicKeyAlgorithm& publicKeyAlg,
|
||||
TestDigestAlgorithmID digestAlg,
|
||||
const ByteString& algorithmIdentifier, bool accepted);
|
||||
|
||||
TestPublicKeyAlgorithm publicKeyAlg;
|
||||
TestDigestAlgorithmID digestAlg;
|
||||
ByteString algorithmIdentifier;
|
||||
bool accepted;
|
||||
};
|
||||
|
||||
TestSignatureAlgorithm md2WithRSAEncryption();
|
||||
TestSignatureAlgorithm md5WithRSAEncryption();
|
||||
TestSignatureAlgorithm sha1WithRSAEncryption();
|
||||
TestSignatureAlgorithm sha256WithRSAEncryption();
|
||||
|
||||
// e.g. YMDHMS(2016, 12, 31, 1, 23, 45) => 2016-12-31:01:23:45 (GMT)
|
||||
mozilla::pkix::Time YMDHMS(uint16_t year, uint16_t month, uint16_t day,
|
||||
uint16_t hour, uint16_t minutes, uint16_t seconds);
|
||||
|
||||
ByteString TLV(uint8_t tag, size_t length, const ByteString& value);
|
||||
|
||||
inline ByteString TLV(uint8_t tag, const ByteString& value) {
|
||||
return TLV(tag, value.length(), value);
|
||||
}
|
||||
|
||||
// Although we can't enforce it without relying on Cuser-defined literals,
|
||||
// which aren't supported by all of our compilers yet, you should only pass
|
||||
// string literals as the last parameter to the following two functions.
|
||||
|
||||
template <size_t N>
|
||||
inline ByteString TLV(uint8_t tag, const char (&value)[N]) {
|
||||
static_assert(N > 0, "cannot have string literal of size 0");
|
||||
assert(value[N - 1] == 0);
|
||||
return TLV(tag, ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1));
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
inline ByteString TLV(uint8_t tag, size_t length, const char (&value)[N]) {
|
||||
static_assert(N > 0, "cannot have string literal of size 0");
|
||||
assert(value[N - 1] == 0);
|
||||
return TLV(tag, length,
|
||||
ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1));
|
||||
}
|
||||
|
||||
ByteString Boolean(bool value);
|
||||
ByteString Integer(long value);
|
||||
|
||||
ByteString CN(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/);
|
||||
|
||||
inline ByteString CN(const char* value,
|
||||
uint8_t encodingTag = 0x0c /*UTF8String*/) {
|
||||
return CN(
|
||||
ByteString(reinterpret_cast<const uint8_t*>(value), std::strlen(value)),
|
||||
encodingTag);
|
||||
}
|
||||
|
||||
ByteString OU(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/);
|
||||
|
||||
inline ByteString OU(const char* value,
|
||||
uint8_t encodingTag = 0x0c /*UTF8String*/) {
|
||||
return OU(
|
||||
ByteString(reinterpret_cast<const uint8_t*>(value), std::strlen(value)),
|
||||
encodingTag);
|
||||
}
|
||||
|
||||
ByteString emailAddress(const ByteString&);
|
||||
|
||||
inline ByteString emailAddress(const char* value) {
|
||||
return emailAddress(
|
||||
ByteString(reinterpret_cast<const uint8_t*>(value), std::strlen(value)));
|
||||
}
|
||||
|
||||
// RelativeDistinguishedName ::=
|
||||
// SET SIZE (1..MAX) OF AttributeTypeAndValue
|
||||
//
|
||||
ByteString RDN(const ByteString& avas);
|
||||
|
||||
// Name ::= CHOICE { -- only one possibility for now --
|
||||
// rdnSequence RDNSequence }
|
||||
//
|
||||
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
||||
//
|
||||
ByteString Name(const ByteString& rdns);
|
||||
|
||||
inline ByteString CNToDERName(const ByteString& cn) {
|
||||
return Name(RDN(CN(cn)));
|
||||
}
|
||||
|
||||
inline ByteString CNToDERName(const char* cn) { return Name(RDN(CN(cn))); }
|
||||
|
||||
// GeneralName ::= CHOICE {
|
||||
// otherName [0] OtherName,
|
||||
// rfc822Name [1] IA5String,
|
||||
// dNSName [2] IA5String,
|
||||
// x400Address [3] ORAddress,
|
||||
// directoryName [4] Name,
|
||||
// ediPartyName [5] EDIPartyName,
|
||||
// uniformResourceIdentifier [6] IA5String,
|
||||
// iPAddress [7] OCTET STRING,
|
||||
// registeredID [8] OBJECT IDENTIFIER }
|
||||
|
||||
inline ByteString RFC822Name(const ByteString& name) {
|
||||
// (2 << 6) means "context-specific", 1 is the GeneralName tag.
|
||||
return TLV((2 << 6) | 1, name);
|
||||
}
|
||||
|
||||
template <size_t L>
|
||||
inline ByteString RFC822Name(const char (&bytes)[L]) {
|
||||
return RFC822Name(
|
||||
ByteString(reinterpret_cast<const uint8_t*>(&bytes), L - 1));
|
||||
}
|
||||
|
||||
inline ByteString DNSName(const ByteString& name) {
|
||||
// (2 << 6) means "context-specific", 2 is the GeneralName tag.
|
||||
return TLV((2 << 6) | 2, name);
|
||||
}
|
||||
|
||||
template <size_t L>
|
||||
inline ByteString DNSName(const char (&bytes)[L]) {
|
||||
return DNSName(ByteString(reinterpret_cast<const uint8_t*>(&bytes), L - 1));
|
||||
}
|
||||
|
||||
inline ByteString DirectoryName(const ByteString& name) {
|
||||
// (2 << 6) means "context-specific", (1 << 5) means "constructed", and 4 is
|
||||
// the DirectoryName tag.
|
||||
return TLV((2 << 6) | (1 << 5) | 4, name);
|
||||
}
|
||||
|
||||
inline ByteString IPAddress() {
|
||||
// (2 << 6) means "context-specific", 7 is the GeneralName tag.
|
||||
return TLV((2 << 6) | 7, ByteString());
|
||||
}
|
||||
|
||||
template <size_t L>
|
||||
inline ByteString IPAddress(const uint8_t (&bytes)[L]) {
|
||||
// (2 << 6) means "context-specific", 7 is the GeneralName tag.
|
||||
return TLV((2 << 6) | 7, ByteString(bytes, L));
|
||||
}
|
||||
|
||||
// Names should be zero or more GeneralNames, like DNSName and IPAddress return,
|
||||
// concatenated together.
|
||||
//
|
||||
// CreatedEncodedSubjectAltName(ByteString()) results in a SAN with an empty
|
||||
// sequence. CreateEmptyEncodedSubjectName() results in a SAN without any
|
||||
// sequence.
|
||||
ByteString CreateEncodedSubjectAltName(const ByteString& names);
|
||||
ByteString CreateEncodedEmptySubjectAltName();
|
||||
|
||||
class TestKeyPair {
|
||||
public:
|
||||
virtual ~TestKeyPair() {}
|
||||
|
||||
const TestPublicKeyAlgorithm publicKeyAlg;
|
||||
|
||||
// The DER encoding of the entire SubjectPublicKeyInfo structure. This is
|
||||
// what is encoded in certificates.
|
||||
const ByteString subjectPublicKeyInfo;
|
||||
|
||||
// The DER encoding of subjectPublicKeyInfo.subjectPublicKey. This is what is
|
||||
// hashed to create CertIDs for OCSP.
|
||||
const ByteString subjectPublicKey;
|
||||
|
||||
virtual Result SignData(const ByteString& tbs,
|
||||
const TestSignatureAlgorithm& signatureAlgorithm,
|
||||
/*out*/ ByteString& signature) const = 0;
|
||||
|
||||
virtual TestKeyPair* Clone() const = 0;
|
||||
|
||||
protected:
|
||||
TestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg,
|
||||
const ByteString& spk);
|
||||
TestKeyPair(const TestKeyPair&) = delete;
|
||||
void operator=(const TestKeyPair&) = delete;
|
||||
};
|
||||
|
||||
TestKeyPair* CloneReusedKeyPair();
|
||||
TestKeyPair* GenerateKeyPair();
|
||||
TestKeyPair* GenerateDSSKeyPair();
|
||||
inline void DeleteTestKeyPair(TestKeyPair* keyPair) { delete keyPair; }
|
||||
typedef std::unique_ptr<TestKeyPair> ScopedTestKeyPair;
|
||||
|
||||
Result TestVerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo);
|
||||
Result TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo);
|
||||
Result TestDigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestBufLen);
|
||||
|
||||
// Replace one substring in item with another of the same length, but only if
|
||||
// the substring was found exactly once. The "same length" restriction is
|
||||
// useful for avoiding invalidating lengths encoded within the item. The
|
||||
// "only once" restriction is helpful for avoiding making accidental changes.
|
||||
//
|
||||
// The string to search for must be 8 or more bytes long so that it is
|
||||
// extremely unlikely that there will ever be any false positive matches
|
||||
// in digital signatures, keys, hashes, etc.
|
||||
Result TamperOnce(/*in/out*/ ByteString& item, const ByteString& from,
|
||||
const ByteString& to);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Encode Certificates
|
||||
|
||||
enum Version { v1 = 0, v2 = 1, v3 = 2 };
|
||||
|
||||
// signature is assumed to be the DER encoding of an AlgorithmIdentifer. It is
|
||||
// put into the signature field of the TBSCertificate. In most cases, it will
|
||||
// be the same as signatureAlgorithm, which is the algorithm actually used
|
||||
// to sign the certificate.
|
||||
// serialNumber is assumed to be the DER encoding of an INTEGER.
|
||||
//
|
||||
// If extensions is null, then no extensions will be encoded. Otherwise,
|
||||
// extensions must point to an array of ByteStrings, terminated with an empty
|
||||
// ByteString. (If the first item of the array is empty then an empty
|
||||
// Extensions sequence will be encoded.)
|
||||
ByteString CreateEncodedCertificate(
|
||||
long version, const TestSignatureAlgorithm& signature,
|
||||
const ByteString& serialNumber, const ByteString& issuerNameDER,
|
||||
time_t notBefore, time_t notAfter, const ByteString& subjectNameDER,
|
||||
const TestKeyPair& subjectKeyPair,
|
||||
/*optional*/ const ByteString* extensions, const TestKeyPair& issuerKeyPair,
|
||||
const TestSignatureAlgorithm& signatureAlgorithm);
|
||||
|
||||
ByteString CreateEncodedSerialNumber(long value);
|
||||
|
||||
enum class Critical { No = 0, Yes = 1 };
|
||||
|
||||
ByteString CreateEncodedBasicConstraints(
|
||||
bool isCA,
|
||||
/*optional in*/ const long* pathLenConstraint, Critical critical);
|
||||
|
||||
// Creates a DER-encoded extKeyUsage extension with one EKU OID.
|
||||
ByteString CreateEncodedEKUExtension(Input eku, Critical critical);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Encode OCSP responses
|
||||
|
||||
class OCSPResponseExtension final {
|
||||
public:
|
||||
OCSPResponseExtension();
|
||||
|
||||
ByteString id;
|
||||
bool critical;
|
||||
ByteString value;
|
||||
OCSPResponseExtension* next;
|
||||
};
|
||||
|
||||
class OCSPResponseContext final {
|
||||
public:
|
||||
OCSPResponseContext(const CertID& certID, std::time_t time);
|
||||
|
||||
const CertID& certID;
|
||||
// TODO(bug 980538): add a way to specify what certificates are included.
|
||||
|
||||
// The fields below are in the order that they appear in an OCSP response.
|
||||
|
||||
enum OCSPResponseStatus {
|
||||
successful = 0,
|
||||
malformedRequest = 1,
|
||||
internalError = 2,
|
||||
tryLater = 3,
|
||||
// 4 is not used
|
||||
sigRequired = 5,
|
||||
unauthorized = 6,
|
||||
};
|
||||
uint8_t responseStatus; // an OCSPResponseStatus or an invalid value
|
||||
bool skipResponseBytes; // If true, don't include responseBytes
|
||||
|
||||
// responderID
|
||||
ByteString signerNameDER; // If set, responderID will use the byName
|
||||
// form; otherwise responderID will use the
|
||||
// byKeyHash form.
|
||||
|
||||
std::time_t producedAt;
|
||||
|
||||
// SingleResponse extensions (for the certID given in the constructor).
|
||||
OCSPResponseExtension* singleExtensions;
|
||||
// ResponseData extensions.
|
||||
OCSPResponseExtension* responseExtensions;
|
||||
bool includeEmptyExtensions; // If true, include the extension wrapper
|
||||
// regardless of if there are any actual
|
||||
// extensions.
|
||||
ScopedTestKeyPair signerKeyPair;
|
||||
TestSignatureAlgorithm signatureAlgorithm;
|
||||
bool badSignature; // If true, alter the signature to fail verification
|
||||
const ByteString* certs; // optional; array terminated by an empty string
|
||||
|
||||
// The following fields are on a per-SingleResponse basis. In the future we
|
||||
// may support including multiple SingleResponses per response.
|
||||
enum CertStatus {
|
||||
good = 0,
|
||||
revoked = 1,
|
||||
unknown = 2,
|
||||
};
|
||||
uint8_t certStatus; // CertStatus or an invalid value
|
||||
std::time_t revocationTime; // For certStatus == revoked
|
||||
std::time_t thisUpdate;
|
||||
std::time_t nextUpdate;
|
||||
bool includeNextUpdate;
|
||||
};
|
||||
|
||||
ByteString CreateEncodedOCSPResponse(OCSPResponseContext& context);
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::pkix::test
|
||||
|
||||
#endif // mozilla_pkix_test_pkixtestutil_h
|
|
@ -0,0 +1,310 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_Input_h
|
||||
#define mozilla_pkix_Input_h
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "mozpkix/Result.h"
|
||||
#include "stdint.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
class Reader;
|
||||
|
||||
// An Input is a safety-oriented immutable weak reference to a array of bytes
|
||||
// of a known size. The data can only be legally accessed by constructing a
|
||||
// Reader object, which guarantees all accesses to the data are memory safe.
|
||||
// Neither Input not Reader provide any facilities for modifying the data
|
||||
// they reference.
|
||||
//
|
||||
// Inputs are small and should usually be passed by value, not by reference,
|
||||
// though for inline functions the distinction doesn't matter:
|
||||
//
|
||||
// Result GoodExample(Input input);
|
||||
// Result BadExample(const Input& input);
|
||||
// Result WorseExample(const uint8_t* input, size_t len);
|
||||
//
|
||||
// Note that in the example, GoodExample has the same performance
|
||||
// characteristics as WorseExample, but with much better safety guarantees.
|
||||
class Input final {
|
||||
public:
|
||||
typedef uint16_t size_type;
|
||||
|
||||
// This constructor is useful for inputs that are statically known to be of a
|
||||
// fixed size, e.g.:
|
||||
//
|
||||
// static const uint8_t EXPECTED_BYTES[] = { 0x00, 0x01, 0x02 };
|
||||
// const Input expected(EXPECTED_BYTES);
|
||||
//
|
||||
// This is equivalent to (and preferred over):
|
||||
//
|
||||
// static const uint8_t EXPECTED_BYTES[] = { 0x00, 0x01, 0x02 };
|
||||
// Input expected;
|
||||
// Result rv = expected.Init(EXPECTED_BYTES, sizeof EXPECTED_BYTES);
|
||||
template <size_type N>
|
||||
explicit Input(const uint8_t (&aData)[N]) : data(aData), len(N) {}
|
||||
|
||||
// Construct a valid, empty, Init-able Input.
|
||||
Input() : data(nullptr), len(0u) {}
|
||||
|
||||
// This is intentionally not explicit in order to allow value semantics.
|
||||
Input(const Input&) = default;
|
||||
|
||||
// Initialize the input. data must be non-null and len must be less than
|
||||
// 65536. Init may not be called more than once.
|
||||
Result Init(const uint8_t* aData, size_t aLen) {
|
||||
if (this->data) {
|
||||
// already initialized
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
if (!aData || aLen > 0xffffu) {
|
||||
// input too large
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
|
||||
this->data = aData;
|
||||
this->len = aLen;
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
// Initialize the input to be equivalent to the given input. Init may not be
|
||||
// called more than once.
|
||||
//
|
||||
// This is basically operator=, but it wasn't given that name because
|
||||
// normally callers do not check the result of operator=, and normally
|
||||
// operator= can be used multiple times.
|
||||
Result Init(Input other) { return Init(other.data, other.len); }
|
||||
|
||||
// Returns the length of the input.
|
||||
//
|
||||
// Having the return type be size_type instead of size_t avoids the need for
|
||||
// callers to ensure that the result is small enough.
|
||||
size_type GetLength() const { return static_cast<size_type>(len); }
|
||||
|
||||
// Don't use this. It is here because we have some "friend" functions that we
|
||||
// don't want to declare in this header file.
|
||||
const uint8_t* UnsafeGetData() const { return data; }
|
||||
|
||||
private:
|
||||
const uint8_t* data;
|
||||
size_t len;
|
||||
|
||||
void operator=(const Input&) = delete; // Use Init instead.
|
||||
};
|
||||
|
||||
inline bool InputsAreEqual(const Input& a, const Input& b) {
|
||||
return a.GetLength() == b.GetLength() &&
|
||||
std::equal(a.UnsafeGetData(), a.UnsafeGetData() + a.GetLength(),
|
||||
b.UnsafeGetData());
|
||||
}
|
||||
|
||||
// An Reader is a cursor/iterator through the contents of an Input, designed to
|
||||
// maximize safety during parsing while minimizing the performance cost of that
|
||||
// safety. In particular, all methods do strict bounds checking to ensure
|
||||
// buffer overflows are impossible, and they are all inline so that the
|
||||
// compiler can coalesce as many of those checks together as possible.
|
||||
//
|
||||
// In general, Reader allows for one byte of lookahead and no backtracking.
|
||||
// However, the Match* functions internally may have more lookahead.
|
||||
class Reader final {
|
||||
public:
|
||||
Reader() : input(nullptr), end(nullptr) {}
|
||||
|
||||
explicit Reader(Input aInput)
|
||||
: input(aInput.UnsafeGetData()),
|
||||
end(aInput.UnsafeGetData() + aInput.GetLength()) {}
|
||||
|
||||
Result Init(Input aInput) {
|
||||
if (this->input) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
this->input = aInput.UnsafeGetData();
|
||||
this->end = aInput.UnsafeGetData() + aInput.GetLength();
|
||||
return Success;
|
||||
}
|
||||
|
||||
bool Peek(uint8_t expectedByte) const {
|
||||
return input < end && *input == expectedByte;
|
||||
}
|
||||
|
||||
Result Read(uint8_t& out) {
|
||||
Result rv = EnsureLength(1);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
out = *input++;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result Read(uint16_t& out) {
|
||||
Result rv = EnsureLength(2);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
out = *input++;
|
||||
out <<= 8u;
|
||||
out |= *input++;
|
||||
return Success;
|
||||
}
|
||||
|
||||
template <Input::size_type N>
|
||||
bool MatchRest(const uint8_t (&toMatch)[N]) {
|
||||
// Normally we use EnsureLength which compares (input + len < end), but
|
||||
// here we want to be sure that there is nothing following the matched
|
||||
// bytes
|
||||
if (static_cast<size_t>(end - input) != N) {
|
||||
return false;
|
||||
}
|
||||
if (!std::equal(input, end, toMatch)) {
|
||||
return false;
|
||||
}
|
||||
input = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MatchRest(Input toMatch) {
|
||||
// Normally we use EnsureLength which compares (input + len < end), but
|
||||
// here we want to be sure that there is nothing following the matched
|
||||
// bytes
|
||||
size_t remaining = static_cast<size_t>(end - input);
|
||||
if (toMatch.GetLength() != remaining) {
|
||||
return false;
|
||||
}
|
||||
if (!std::equal(input, end, toMatch.UnsafeGetData())) {
|
||||
return false;
|
||||
}
|
||||
input = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
Result Skip(Input::size_type len) {
|
||||
Result rv = EnsureLength(len);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
input += len;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result Skip(Input::size_type len, Reader& skipped) {
|
||||
Result rv = EnsureLength(len);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = skipped.Init(input, len);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
input += len;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result Skip(Input::size_type len, /*out*/ Input& skipped) {
|
||||
Result rv = EnsureLength(len);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = skipped.Init(input, len);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
input += len;
|
||||
return Success;
|
||||
}
|
||||
|
||||
void SkipToEnd() { input = end; }
|
||||
|
||||
Result SkipToEnd(/*out*/ Input& skipped) {
|
||||
return Skip(static_cast<Input::size_type>(end - input), skipped);
|
||||
}
|
||||
|
||||
Result EnsureLength(Input::size_type len) {
|
||||
if (static_cast<size_t>(end - input) < len) {
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
bool AtEnd() const { return input == end; }
|
||||
|
||||
class Mark final {
|
||||
public:
|
||||
Mark(const Mark&) = default; // Intentionally not explicit.
|
||||
private:
|
||||
friend class Reader;
|
||||
Mark(const Reader& aInput, const uint8_t* aMark)
|
||||
: input(aInput), mark(aMark) {}
|
||||
const Reader& input;
|
||||
const uint8_t* const mark;
|
||||
void operator=(const Mark&) = delete;
|
||||
};
|
||||
|
||||
Mark GetMark() const { return Mark(*this, input); }
|
||||
|
||||
Result GetInput(const Mark& mark, /*out*/ Input& item) {
|
||||
if (&mark.input != this || mark.mark > input) {
|
||||
return NotReached("invalid mark", Result::FATAL_ERROR_INVALID_ARGS);
|
||||
}
|
||||
return item.Init(mark.mark,
|
||||
static_cast<Input::size_type>(input - mark.mark));
|
||||
}
|
||||
|
||||
private:
|
||||
Result Init(const uint8_t* data, Input::size_type len) {
|
||||
if (input) {
|
||||
// already initialized
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
input = data;
|
||||
end = data + len;
|
||||
return Success;
|
||||
}
|
||||
|
||||
const uint8_t* input;
|
||||
const uint8_t* end;
|
||||
|
||||
Reader(const Reader&) = delete;
|
||||
void operator=(const Reader&) = delete;
|
||||
};
|
||||
|
||||
inline bool InputContains(const Input& input, uint8_t toFind) {
|
||||
Reader reader(input);
|
||||
for (;;) {
|
||||
uint8_t b;
|
||||
if (reader.Read(b) != Success) {
|
||||
return false;
|
||||
}
|
||||
if (b == toFind) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
#endif // mozilla_pkix_Input_h
|
|
@ -0,0 +1,219 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_Result_h
|
||||
#define mozilla_pkix_Result_h
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
static const unsigned int FATAL_ERROR_FLAG = 0x800;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// SELECTED ERROR CODE EXPLANATIONS
|
||||
//
|
||||
// Result::ERROR_UNTRUSTED_CERT
|
||||
// means that the end-entity certificate was actively distrusted.
|
||||
// Result::ERROR_UNTRUSTED_ISSUER
|
||||
// means that path building failed because of active distrust.
|
||||
// Result::ERROR_INVALID_DER_TIME
|
||||
// means the DER-encoded time was unexpected, such as being before the
|
||||
// UNIX epoch (allowed by X500, but not valid here).
|
||||
// Result::ERROR_EXPIRED_CERTIFICATE
|
||||
// means the end entity certificate expired.
|
||||
// Result::ERROR_EXPIRED_ISSUER_CERTIFICATE
|
||||
// means the CA certificate expired.
|
||||
// Result::ERROR_UNKNOWN_ISSUER
|
||||
// means that the CA could not be found in the root store.
|
||||
// Result::ERROR_POLICY_VALIDATION_FAILED
|
||||
// means that an encoded policy could not be applied or wasn't present
|
||||
// when expected. Usually this is in the context of Extended Validation.
|
||||
// Result::ERROR_BAD_CERT_DOMAIN
|
||||
// means that the certificate's name couldn't be matched to the
|
||||
// reference identifier.
|
||||
// Result::ERROR_CERT_NOT_IN_NAME_SPACE
|
||||
// typically means the certificate violates name constraints applied
|
||||
// by the issuer.
|
||||
// Result::ERROR_BAD_DER
|
||||
// means the input was improperly encoded.
|
||||
// Result::ERROR_UNKNOWN_ERROR
|
||||
// means that an external library (NSS) provided an error we didn't
|
||||
// anticipate. See the map below in Result.h to add new ones.
|
||||
// Result::FATAL_ERROR_LIBRARY_FAILURE
|
||||
// is an unexpected fatal error indicating a library had an unexpected
|
||||
// failure, and we can't proceed.
|
||||
// Result::FATAL_ERROR_INVALID_ARGS
|
||||
// means that we violated our own expectations on inputs and there's a
|
||||
// bug somewhere.
|
||||
// Result::FATAL_ERROR_INVALID_STATE
|
||||
// means that we violated our own expectations on state and there's a
|
||||
// bug somewhere.
|
||||
// Result::FATAL_ERROR_NO_MEMORY
|
||||
// means a memory allocation failed, prohibiting validation.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// The first argument to MOZILLA_PKIX_MAP() is used for building the mapping
|
||||
// from error code to error name in MapResultToName.
|
||||
//
|
||||
// The second argument is for defining the value for the enum literal in the
|
||||
// Result enum class.
|
||||
//
|
||||
// The third argument to MOZILLA_PKIX_MAP() is used, along with the first
|
||||
// argument, for maintaining the mapping of mozilla::pkix error codes to
|
||||
// NSS/NSPR error codes in pkixnss.cpp.
|
||||
#define MOZILLA_PKIX_MAP_LIST \
|
||||
MOZILLA_PKIX_MAP(Success, 0, 0) \
|
||||
MOZILLA_PKIX_MAP(ERROR_BAD_DER, 1, SEC_ERROR_BAD_DER) \
|
||||
MOZILLA_PKIX_MAP(ERROR_CA_CERT_INVALID, 2, SEC_ERROR_CA_CERT_INVALID) \
|
||||
MOZILLA_PKIX_MAP(ERROR_BAD_SIGNATURE, 3, SEC_ERROR_BAD_SIGNATURE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_CERT_BAD_ACCESS_LOCATION, 4, \
|
||||
SEC_ERROR_CERT_BAD_ACCESS_LOCATION) \
|
||||
MOZILLA_PKIX_MAP(ERROR_CERT_NOT_IN_NAME_SPACE, 5, \
|
||||
SEC_ERROR_CERT_NOT_IN_NAME_SPACE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, 6, \
|
||||
SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED) \
|
||||
MOZILLA_PKIX_MAP(ERROR_CONNECT_REFUSED, 7, PR_CONNECT_REFUSED_ERROR) \
|
||||
MOZILLA_PKIX_MAP(ERROR_EXPIRED_CERTIFICATE, 8, \
|
||||
SEC_ERROR_EXPIRED_CERTIFICATE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_EXTENSION_VALUE_INVALID, 9, \
|
||||
SEC_ERROR_EXTENSION_VALUE_INVALID) \
|
||||
MOZILLA_PKIX_MAP(ERROR_INADEQUATE_CERT_TYPE, 10, \
|
||||
SEC_ERROR_INADEQUATE_CERT_TYPE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_INADEQUATE_KEY_USAGE, 11, \
|
||||
SEC_ERROR_INADEQUATE_KEY_USAGE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_INVALID_ALGORITHM, 12, SEC_ERROR_INVALID_ALGORITHM) \
|
||||
MOZILLA_PKIX_MAP(ERROR_INVALID_DER_TIME, 13, SEC_ERROR_INVALID_TIME) \
|
||||
MOZILLA_PKIX_MAP(ERROR_KEY_PINNING_FAILURE, 14, \
|
||||
MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_PATH_LEN_CONSTRAINT_INVALID, 15, \
|
||||
SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID) \
|
||||
MOZILLA_PKIX_MAP(ERROR_POLICY_VALIDATION_FAILED, 16, \
|
||||
SEC_ERROR_POLICY_VALIDATION_FAILED) \
|
||||
MOZILLA_PKIX_MAP(ERROR_REVOKED_CERTIFICATE, 17, \
|
||||
SEC_ERROR_REVOKED_CERTIFICATE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNKNOWN_CRITICAL_EXTENSION, 18, \
|
||||
SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNKNOWN_ERROR, 19, PR_UNKNOWN_ERROR) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNKNOWN_ISSUER, 20, SEC_ERROR_UNKNOWN_ISSUER) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNTRUSTED_CERT, 21, SEC_ERROR_UNTRUSTED_CERT) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNTRUSTED_ISSUER, 22, SEC_ERROR_UNTRUSTED_ISSUER) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_BAD_SIGNATURE, 23, SEC_ERROR_OCSP_BAD_SIGNATURE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_INVALID_SIGNING_CERT, 24, \
|
||||
SEC_ERROR_OCSP_INVALID_SIGNING_CERT) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_MALFORMED_REQUEST, 25, \
|
||||
SEC_ERROR_OCSP_MALFORMED_REQUEST) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_MALFORMED_RESPONSE, 26, \
|
||||
SEC_ERROR_OCSP_MALFORMED_RESPONSE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_OLD_RESPONSE, 27, SEC_ERROR_OCSP_OLD_RESPONSE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_REQUEST_NEEDS_SIG, 28, \
|
||||
SEC_ERROR_OCSP_REQUEST_NEEDS_SIG) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_RESPONDER_CERT_INVALID, 29, \
|
||||
SEC_ERROR_OCSP_RESPONDER_CERT_INVALID) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_SERVER_ERROR, 30, SEC_ERROR_OCSP_SERVER_ERROR) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_TRY_SERVER_LATER, 31, \
|
||||
SEC_ERROR_OCSP_TRY_SERVER_LATER) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_UNAUTHORIZED_REQUEST, 32, \
|
||||
SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_UNKNOWN_RESPONSE_STATUS, 33, \
|
||||
SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_UNKNOWN_CERT, 34, SEC_ERROR_OCSP_UNKNOWN_CERT) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_FUTURE_RESPONSE, 35, \
|
||||
SEC_ERROR_OCSP_FUTURE_RESPONSE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_INVALID_KEY, 36, SEC_ERROR_INVALID_KEY) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNSUPPORTED_KEYALG, 37, SEC_ERROR_UNSUPPORTED_KEYALG) \
|
||||
MOZILLA_PKIX_MAP(ERROR_EXPIRED_ISSUER_CERTIFICATE, 38, \
|
||||
SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_CA_CERT_USED_AS_END_ENTITY, 39, \
|
||||
MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY) \
|
||||
MOZILLA_PKIX_MAP(ERROR_INADEQUATE_KEY_SIZE, 40, \
|
||||
MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_V1_CERT_USED_AS_CA, 41, \
|
||||
MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA) \
|
||||
MOZILLA_PKIX_MAP(ERROR_BAD_CERT_DOMAIN, 42, SSL_ERROR_BAD_CERT_DOMAIN) \
|
||||
MOZILLA_PKIX_MAP(ERROR_NO_RFC822NAME_MATCH, 43, \
|
||||
MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNSUPPORTED_ELLIPTIC_CURVE, 44, \
|
||||
SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_NOT_YET_VALID_CERTIFICATE, 45, \
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE, 46, \
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE) \
|
||||
MOZILLA_PKIX_MAP(ERROR_UNSUPPORTED_EC_POINT_FORM, 47, \
|
||||
SEC_ERROR_UNSUPPORTED_EC_POINT_FORM) \
|
||||
MOZILLA_PKIX_MAP(ERROR_SIGNATURE_ALGORITHM_MISMATCH, 48, \
|
||||
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH) \
|
||||
MOZILLA_PKIX_MAP(ERROR_OCSP_RESPONSE_FOR_CERT_MISSING, 49, \
|
||||
MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING) \
|
||||
MOZILLA_PKIX_MAP(ERROR_VALIDITY_TOO_LONG, 50, \
|
||||
MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG) \
|
||||
MOZILLA_PKIX_MAP(ERROR_REQUIRED_TLS_FEATURE_MISSING, 51, \
|
||||
MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING) \
|
||||
MOZILLA_PKIX_MAP(ERROR_INVALID_INTEGER_ENCODING, 52, \
|
||||
MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING) \
|
||||
MOZILLA_PKIX_MAP(ERROR_EMPTY_ISSUER_NAME, 53, \
|
||||
MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME) \
|
||||
MOZILLA_PKIX_MAP(ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED, 54, \
|
||||
MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED) \
|
||||
MOZILLA_PKIX_MAP(ERROR_SELF_SIGNED_CERT, 55, \
|
||||
MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT) \
|
||||
MOZILLA_PKIX_MAP(ERROR_MITM_DETECTED, 56, MOZILLA_PKIX_ERROR_MITM_DETECTED) \
|
||||
MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_ARGS, FATAL_ERROR_FLAG | 1, \
|
||||
SEC_ERROR_INVALID_ARGS) \
|
||||
MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_STATE, FATAL_ERROR_FLAG | 2, \
|
||||
PR_INVALID_STATE_ERROR) \
|
||||
MOZILLA_PKIX_MAP(FATAL_ERROR_LIBRARY_FAILURE, FATAL_ERROR_FLAG | 3, \
|
||||
SEC_ERROR_LIBRARY_FAILURE) \
|
||||
MOZILLA_PKIX_MAP(FATAL_ERROR_NO_MEMORY, FATAL_ERROR_FLAG | 4, \
|
||||
SEC_ERROR_NO_MEMORY) \
|
||||
/* nothing here */
|
||||
|
||||
enum class Result {
|
||||
#define MOZILLA_PKIX_MAP(name, value, nss_name) name = value,
|
||||
MOZILLA_PKIX_MAP_LIST
|
||||
#undef MOZILLA_PKIX_MAP
|
||||
};
|
||||
|
||||
// Returns the stringified name of the given result, e.g. "Result::Success",
|
||||
// or nullptr if result is unknown (invalid).
|
||||
const char* MapResultToName(Result result);
|
||||
|
||||
// We write many comparisons as (x != Success), and this shortened name makes
|
||||
// those comparisons clearer, especially because the shortened name often
|
||||
// results in less line wrapping.
|
||||
static const Result Success = Result::Success;
|
||||
|
||||
inline bool IsFatalError(Result rv) {
|
||||
return (static_cast<unsigned int>(rv) & FATAL_ERROR_FLAG) != 0;
|
||||
}
|
||||
|
||||
inline Result NotReached(const char* /*explanation*/, Result result) {
|
||||
assert(false);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
#endif // mozilla_pkix_Result_h
|
|
@ -0,0 +1,137 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2014 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_Time_h
|
||||
#define mozilla_pkix_Time_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ctime>
|
||||
#include <limits>
|
||||
|
||||
#include "mozpkix/Result.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
// Time with a range from the first second of year 0 (AD) through at least the
|
||||
// last second of year 9999, which is the range of legal times in X.509 and
|
||||
// OCSP. This type has second-level precision. The time zone is always UTC.
|
||||
//
|
||||
// Pass by value, not by reference.
|
||||
class Time final {
|
||||
public:
|
||||
// Construct an uninitialized instance.
|
||||
//
|
||||
// This will fail to compile because there is no default constructor:
|
||||
// Time x;
|
||||
//
|
||||
// This will succeed, leaving the time uninitialized:
|
||||
// Time x(Time::uninitialized);
|
||||
enum Uninitialized { uninitialized };
|
||||
explicit Time(Uninitialized) {}
|
||||
|
||||
bool operator==(const Time& other) const {
|
||||
return elapsedSecondsAD == other.elapsedSecondsAD;
|
||||
}
|
||||
bool operator>(const Time& other) const {
|
||||
return elapsedSecondsAD > other.elapsedSecondsAD;
|
||||
}
|
||||
bool operator>=(const Time& other) const {
|
||||
return elapsedSecondsAD >= other.elapsedSecondsAD;
|
||||
}
|
||||
bool operator<(const Time& other) const {
|
||||
return elapsedSecondsAD < other.elapsedSecondsAD;
|
||||
}
|
||||
bool operator<=(const Time& other) const {
|
||||
return elapsedSecondsAD <= other.elapsedSecondsAD;
|
||||
}
|
||||
|
||||
Result AddSeconds(uint64_t seconds) {
|
||||
if (std::numeric_limits<uint64_t>::max() - elapsedSecondsAD < seconds) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS; // integer overflow
|
||||
}
|
||||
elapsedSecondsAD += seconds;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result SubtractSeconds(uint64_t seconds) {
|
||||
if (seconds > elapsedSecondsAD) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS; // integer overflow
|
||||
}
|
||||
elapsedSecondsAD -= seconds;
|
||||
return Success;
|
||||
}
|
||||
|
||||
static const uint64_t ONE_DAY_IN_SECONDS =
|
||||
UINT64_C(24) * UINT64_C(60) * UINT64_C(60);
|
||||
|
||||
private:
|
||||
// This constructor is hidden to prevent accidents like this:
|
||||
//
|
||||
// Time foo(time_t t)
|
||||
// {
|
||||
// // WRONG! 1970-01-01-00:00:00 == time_t(0), but not Time(0)!
|
||||
// return Time(t);
|
||||
// }
|
||||
explicit Time(uint64_t aElapsedSecondsAD)
|
||||
: elapsedSecondsAD(aElapsedSecondsAD) {}
|
||||
friend Time TimeFromElapsedSecondsAD(uint64_t);
|
||||
friend class Duration;
|
||||
|
||||
uint64_t elapsedSecondsAD;
|
||||
};
|
||||
|
||||
inline Time TimeFromElapsedSecondsAD(uint64_t aElapsedSecondsAD) {
|
||||
return Time(aElapsedSecondsAD);
|
||||
}
|
||||
|
||||
Time Now();
|
||||
|
||||
// Note the epoch is the unix epoch (ie 00:00:00 UTC, 1 January 1970)
|
||||
Time TimeFromEpochInSeconds(uint64_t secondsSinceEpoch);
|
||||
|
||||
class Duration final {
|
||||
public:
|
||||
Duration(Time timeA, Time timeB)
|
||||
: durationInSeconds(
|
||||
timeA < timeB ? timeB.elapsedSecondsAD - timeA.elapsedSecondsAD
|
||||
: timeA.elapsedSecondsAD - timeB.elapsedSecondsAD) {}
|
||||
|
||||
explicit Duration(uint64_t aDurationInSeconds)
|
||||
: durationInSeconds(aDurationInSeconds) {}
|
||||
|
||||
bool operator>(const Duration& other) const {
|
||||
return durationInSeconds > other.durationInSeconds;
|
||||
}
|
||||
bool operator<(const Duration& other) const {
|
||||
return durationInSeconds < other.durationInSeconds;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t durationInSeconds;
|
||||
};
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
#endif // mozilla_pkix_Time_h
|
|
@ -0,0 +1,160 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_pkix_h
|
||||
#define mozilla_pkix_pkix_h
|
||||
|
||||
#include "mozpkix/pkixtypes.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// LIMITED SUPPORT FOR CERTIFICATE POLICIES
|
||||
//
|
||||
// If SEC_OID_X509_ANY_POLICY is passed as the value of the requiredPolicy
|
||||
// parameter then all policy validation will be skipped. Otherwise, path
|
||||
// building and validation will be done for the given policy.
|
||||
//
|
||||
// In RFC 5280 terms:
|
||||
//
|
||||
// * user-initial-policy-set = { requiredPolicy }.
|
||||
// * initial-explicit-policy = true
|
||||
// * initial-any-policy-inhibit = false
|
||||
//
|
||||
// We allow intermediate cerificates to use this extension but since
|
||||
// we do not process the inhibit anyPolicy extesion we will fail if this
|
||||
// extension is present. TODO(bug 989051)
|
||||
// Because we force explicit policy and because we prohibit policy mapping, we
|
||||
// do not bother processing the policy mapping, or policy constraint.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// ERROR RANKING
|
||||
//
|
||||
// BuildCertChain prioritizes certain checks ahead of others so that when a
|
||||
// certificate chain has multiple errors, the "most serious" error is
|
||||
// returned. In practice, this ranking of seriousness is tied directly to how
|
||||
// Firefox's certificate error override mechanism.
|
||||
//
|
||||
// The ranking is:
|
||||
//
|
||||
// 1. Active distrust (Result::ERROR_UNTRUSTED_CERT).
|
||||
// 2. Problems with issuer-independent properties for CA certificates.
|
||||
// 3. Unknown issuer (Result::ERROR_UNKNOWN_ISSUER).
|
||||
// 4. Problems with issuer-independent properties for EE certificates.
|
||||
// 5. Revocation.
|
||||
//
|
||||
// In particular, if BuildCertChain returns Result::ERROR_UNKNOWN_ISSUER then
|
||||
// the caller can call CERT_CheckCertValidTimes to determine if the certificate
|
||||
// is ALSO expired.
|
||||
//
|
||||
// It would be better if revocation were prioritized above expiration and
|
||||
// unknown issuer. However, it is impossible to do revocation checking without
|
||||
// knowing the issuer, since the issuer information is needed to validate the
|
||||
// revocation information. Also, generally revocation checking only works
|
||||
// during the validity period of the certificate.
|
||||
//
|
||||
// In general, when path building fails, BuildCertChain will return
|
||||
// Result::ERROR_UNKNOWN_ISSUER. However, if all attempted paths resulted in
|
||||
// the same error (which is trivially true when there is only one potential
|
||||
// path), more specific errors will be returned.
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
// Meanings of specific error codes can be found in Result.h
|
||||
|
||||
// This function attempts to find a trustworthy path from the supplied
|
||||
// certificate to a trust anchor. In the event that no trusted path is found,
|
||||
// the method returns an error result; the error ranking is described above.
|
||||
//
|
||||
// Parameters:
|
||||
// time:
|
||||
// Timestamp for which the chain should be valid; this is useful to
|
||||
// analyze whether a record was trustworthy when it was made.
|
||||
// requiredKeyUsageIfPresent:
|
||||
// What key usage bits must be set, if the extension is present at all,
|
||||
// to be considered a valid chain. Multiple values should be OR'd
|
||||
// together. If you don't want to specify anything, use
|
||||
// KeyUsage::noParticularKeyUsageRequired.
|
||||
// requiredEKUIfPresent:
|
||||
// What extended key usage bits must be set, if the EKU extension
|
||||
// exists, to be considered a valid chain. Multiple values should be
|
||||
// OR'd together. If you don't want to specify anything, use
|
||||
// KeyPurposeId::anyExtendedKeyUsage.
|
||||
// requiredPolicy:
|
||||
// This is the policy to apply; typically included in EV certificates.
|
||||
// If there is no policy, pass in CertPolicyId::anyPolicy.
|
||||
Result BuildCertChain(TrustDomain& trustDomain, Input cert, Time time,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
/*optional*/ const Input* stapledOCSPResponse);
|
||||
|
||||
// Verify that the given end-entity cert, which is assumed to have been already
|
||||
// validated with BuildCertChain, is valid for the given hostname. The matching
|
||||
// function attempts to implement RFC 6125 with a couple of differences:
|
||||
// - IP addresses are out of scope of RFC 6125, but this method accepts them for
|
||||
// backward compatibility (see SearchNames in pkixnames.cpp)
|
||||
// - A wildcard in a DNS-ID may only appear as the entirety of the first label.
|
||||
Result CheckCertHostname(Input cert, Input hostname,
|
||||
NameMatchingPolicy& nameMatchingPolicy);
|
||||
|
||||
// Construct an RFC-6960-encoded OCSP request, ready for submission to a
|
||||
// responder, for the provided CertID. The request has no extensions.
|
||||
static const size_t OCSP_REQUEST_MAX_LENGTH = 127;
|
||||
Result CreateEncodedOCSPRequest(TrustDomain& trustDomain, const CertID& certID,
|
||||
/*out*/ uint8_t (&out)[OCSP_REQUEST_MAX_LENGTH],
|
||||
/*out*/ size_t& outLen);
|
||||
|
||||
// The out parameter expired will be true if the response has expired. If the
|
||||
// response also indicates a revoked or unknown certificate, that error
|
||||
// will be returned. Otherwise, Result::ERROR_OCSP_OLD_RESPONSE will be
|
||||
// returned for an expired response.
|
||||
//
|
||||
// The optional parameter thisUpdate will be the thisUpdate value of
|
||||
// the encoded response if it is considered trustworthy. Only
|
||||
// good, unknown, or revoked responses that verify correctly are considered
|
||||
// trustworthy. If the response is not trustworthy, thisUpdate will be 0.
|
||||
// Similarly, the optional parameter validThrough will be the time through
|
||||
// which the encoded response is considered trustworthy (that is, as long as
|
||||
// the given time at which to validate is less than or equal to validThrough,
|
||||
// the response will be considered trustworthy).
|
||||
Result VerifyEncodedOCSPResponse(
|
||||
TrustDomain& trustDomain, const CertID& certID, Time time,
|
||||
uint16_t maxLifetimeInDays, Input encodedResponse,
|
||||
/* out */ bool& expired,
|
||||
/* optional out */ Time* thisUpdate = nullptr,
|
||||
/* optional out */ Time* validThrough = nullptr);
|
||||
|
||||
// Check that the TLSFeature extensions in a given end-entity cert (which is
|
||||
// assumed to have been already validated with BuildCertChain) are satisfied.
|
||||
// The only feature which we cancurrently process a requirement for is
|
||||
// status_request (OCSP stapling) so we reject any extension that specifies a
|
||||
// requirement for another value. Empty extensions are also rejected.
|
||||
Result CheckTLSFeaturesAreSatisfied(Input& cert,
|
||||
const Input* stapledOCSPResponse);
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
#endif // mozilla_pkix_pkix_h
|
|
@ -0,0 +1,65 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_pkixcheck_h
|
||||
#define mozilla_pkix_pkixcheck_h
|
||||
|
||||
#include "mozpkix/pkixtypes.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
class BackCert;
|
||||
|
||||
Result CheckIssuerIndependentProperties(TrustDomain& trustDomain,
|
||||
const BackCert& cert, Time time,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
unsigned int subCACount,
|
||||
/*out*/ TrustLevel& trustLevel);
|
||||
|
||||
Result CheckNameConstraints(Input encodedNameConstraints,
|
||||
const BackCert& firstChild,
|
||||
KeyPurposeId requiredEKUIfPresent);
|
||||
|
||||
Result CheckIssuer(Input encodedIssuer);
|
||||
|
||||
// ParseValidity and CheckValidity are usually used together. First you parse
|
||||
// the dates from the DER Validity sequence, then you compare them to the time
|
||||
// at which you are validating. They are separate so that the notBefore and
|
||||
// notAfter times can be used for other things before they are checked against
|
||||
// the time of validation.
|
||||
Result ParseValidity(Input encodedValidity,
|
||||
/*optional out*/ Time* notBeforeOut = nullptr,
|
||||
/*optional out*/ Time* notAfterOut = nullptr);
|
||||
Result CheckValidity(Time time, Time notBefore, Time notAfter);
|
||||
|
||||
// Check that a subject has TLS Feature (rfc7633) requirements that match its
|
||||
// potential issuer
|
||||
Result CheckTLSFeatures(const BackCert& subject, BackCert& potentialIssuer);
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
#endif // mozilla_pkix_pkixcheck_h
|
|
@ -0,0 +1,520 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_pkixder_h
|
||||
#define mozilla_pkix_pkixder_h
|
||||
|
||||
// Expect* functions advance the input mark and return Success if the input
|
||||
// matches the given criteria; they fail with the input mark in an undefined
|
||||
// state if the input does not match the criteria.
|
||||
//
|
||||
// Match* functions advance the input mark and return true if the input matches
|
||||
// the given criteria; they return false without changing the input mark if the
|
||||
// input does not match the criteria.
|
||||
//
|
||||
// Skip* functions unconditionally advance the input mark and return Success if
|
||||
// they are able to do so; otherwise they fail with the input mark in an
|
||||
// undefined state.
|
||||
|
||||
#include "mozpkix/Input.h"
|
||||
#include "mozpkix/pkixtypes.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
namespace der {
|
||||
|
||||
enum Class : uint8_t {
|
||||
UNIVERSAL = 0 << 6,
|
||||
// APPLICATION = 1 << 6, // unused
|
||||
CONTEXT_SPECIFIC = 2 << 6,
|
||||
// PRIVATE = 3 << 6 // unused
|
||||
};
|
||||
|
||||
enum Constructed { CONSTRUCTED = 1 << 5 };
|
||||
|
||||
enum Tag : uint8_t {
|
||||
BOOLEAN = UNIVERSAL | 0x01,
|
||||
INTEGER = UNIVERSAL | 0x02,
|
||||
BIT_STRING = UNIVERSAL | 0x03,
|
||||
OCTET_STRING = UNIVERSAL | 0x04,
|
||||
NULLTag = UNIVERSAL | 0x05,
|
||||
OIDTag = UNIVERSAL | 0x06,
|
||||
ENUMERATED = UNIVERSAL | 0x0a,
|
||||
UTF8String = UNIVERSAL | 0x0c,
|
||||
SEQUENCE = UNIVERSAL | CONSTRUCTED | 0x10, // 0x30
|
||||
SET = UNIVERSAL | CONSTRUCTED | 0x11, // 0x31
|
||||
PrintableString = UNIVERSAL | 0x13,
|
||||
TeletexString = UNIVERSAL | 0x14,
|
||||
IA5String = UNIVERSAL | 0x16,
|
||||
UTCTime = UNIVERSAL | 0x17,
|
||||
GENERALIZED_TIME = UNIVERSAL | 0x18,
|
||||
};
|
||||
|
||||
enum class EmptyAllowed { No = 0, Yes = 1 };
|
||||
|
||||
Result ReadTagAndGetValue(Reader& input, /*out*/ uint8_t& tag,
|
||||
/*out*/ Input& value);
|
||||
Result End(Reader& input);
|
||||
|
||||
inline Result ExpectTagAndGetValue(Reader& input, uint8_t tag,
|
||||
/*out*/ Input& value) {
|
||||
uint8_t actualTag;
|
||||
Result rv = ReadTagAndGetValue(input, actualTag, value);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (tag != actualTag) {
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
inline Result ExpectTagAndGetValue(Reader& input, uint8_t tag,
|
||||
/*out*/ Reader& value) {
|
||||
Input valueInput;
|
||||
Result rv = ExpectTagAndGetValue(input, tag, valueInput);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return value.Init(valueInput);
|
||||
}
|
||||
|
||||
inline Result ExpectTagAndEmptyValue(Reader& input, uint8_t tag) {
|
||||
Reader value;
|
||||
Result rv = ExpectTagAndGetValue(input, tag, value);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return End(value);
|
||||
}
|
||||
|
||||
inline Result ExpectTagAndSkipValue(Reader& input, uint8_t tag) {
|
||||
Input ignoredValue;
|
||||
return ExpectTagAndGetValue(input, tag, ignoredValue);
|
||||
}
|
||||
|
||||
// Like ExpectTagAndGetValue, except the output Input will contain the
|
||||
// encoded tag and length along with the value.
|
||||
inline Result ExpectTagAndGetTLV(Reader& input, uint8_t tag,
|
||||
/*out*/ Input& tlv) {
|
||||
Reader::Mark mark(input.GetMark());
|
||||
Result rv = ExpectTagAndSkipValue(input, tag);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return input.GetInput(mark, tlv);
|
||||
}
|
||||
|
||||
inline Result End(Reader& input) {
|
||||
if (!input.AtEnd()) {
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
template <typename Decoder>
|
||||
inline Result Nested(Reader& input, uint8_t tag, Decoder decoder) {
|
||||
Reader nested;
|
||||
Result rv = ExpectTagAndGetValue(input, tag, nested);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = decoder(nested);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return End(nested);
|
||||
}
|
||||
|
||||
template <typename Decoder>
|
||||
inline Result Nested(Reader& input, uint8_t outerTag, uint8_t innerTag,
|
||||
Decoder decoder) {
|
||||
Reader nestedInput;
|
||||
Result rv = ExpectTagAndGetValue(input, outerTag, nestedInput);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = Nested(nestedInput, innerTag, decoder);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return End(nestedInput);
|
||||
}
|
||||
|
||||
// This can be used to decode constructs like this:
|
||||
//
|
||||
// ...
|
||||
// foos SEQUENCE OF Foo,
|
||||
// ...
|
||||
// Foo ::= SEQUENCE {
|
||||
// }
|
||||
//
|
||||
// using code like this:
|
||||
//
|
||||
// Result Foo(Reader& r) { /*...*/ }
|
||||
//
|
||||
// rv = der::NestedOf(input, der::SEQEUENCE, der::SEQUENCE, Foo);
|
||||
//
|
||||
// or:
|
||||
//
|
||||
// Result Bar(Reader& r, int value) { /*...*/ }
|
||||
//
|
||||
// int value = /*...*/;
|
||||
//
|
||||
// rv = der::NestedOf(input, der::SEQUENCE, [value](Reader& r) {
|
||||
// return Bar(r, value);
|
||||
// });
|
||||
//
|
||||
// In these examples the function will get called once for each element of
|
||||
// foos.
|
||||
//
|
||||
template <typename Decoder>
|
||||
inline Result NestedOf(Reader& input, uint8_t outerTag, uint8_t innerTag,
|
||||
EmptyAllowed mayBeEmpty, Decoder decoder) {
|
||||
Reader inner;
|
||||
Result rv = ExpectTagAndGetValue(input, outerTag, inner);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (inner.AtEnd()) {
|
||||
if (mayBeEmpty != EmptyAllowed::Yes) {
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
do {
|
||||
rv = Nested(inner, innerTag, decoder);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
} while (!inner.AtEnd());
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
// Often, a function will need to decode an Input or Reader that contains
|
||||
// DER-encoded data wrapped in a SEQUENCE (or similar) with nothing after it.
|
||||
// This function reduces the boilerplate necessary for stripping the outermost
|
||||
// SEQUENCE (or similar) and ensuring that nothing follows it.
|
||||
inline Result ExpectTagAndGetValueAtEnd(Reader& outer, uint8_t expectedTag,
|
||||
/*out*/ Reader& inner) {
|
||||
Result rv = der::ExpectTagAndGetValue(outer, expectedTag, inner);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return der::End(outer);
|
||||
}
|
||||
|
||||
// Similar to the above, but takes an Input instead of a Reader&.
|
||||
inline Result ExpectTagAndGetValueAtEnd(Input outer, uint8_t expectedTag,
|
||||
/*out*/ Reader& inner) {
|
||||
Reader outerReader(outer);
|
||||
return ExpectTagAndGetValueAtEnd(outerReader, expectedTag, inner);
|
||||
}
|
||||
|
||||
// Universal types
|
||||
|
||||
namespace internal {
|
||||
|
||||
enum class IntegralValueRestriction {
|
||||
NoRestriction,
|
||||
MustBePositive,
|
||||
MustBe0To127,
|
||||
};
|
||||
|
||||
Result IntegralBytes(
|
||||
Reader& input, uint8_t tag, IntegralValueRestriction valueRestriction,
|
||||
/*out*/ Input& value,
|
||||
/*optional out*/ Input::size_type* significantBytes = nullptr);
|
||||
|
||||
// This parser will only parse values between 0..127. If this range is
|
||||
// increased then callers will need to be changed.
|
||||
Result IntegralValue(Reader& input, uint8_t tag, /*out*/ uint8_t& value);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
Result BitStringWithNoUnusedBits(Reader& input, /*out*/ Input& value);
|
||||
|
||||
inline Result Boolean(Reader& input, /*out*/ bool& value) {
|
||||
Reader valueReader;
|
||||
Result rv = ExpectTagAndGetValue(input, BOOLEAN, valueReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
uint8_t intValue;
|
||||
rv = valueReader.Read(intValue);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = End(valueReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
switch (intValue) {
|
||||
case 0:
|
||||
value = false;
|
||||
return Success;
|
||||
case 0xFF:
|
||||
value = true;
|
||||
return Success;
|
||||
default:
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
}
|
||||
|
||||
// This is for BOOLEAN DEFAULT FALSE.
|
||||
// The standard stipulates that "The encoding of a set value or sequence value
|
||||
// shall not include an encoding for any component value which is equal to its
|
||||
// default value." However, it appears to be common that other libraries
|
||||
// incorrectly include the value of a BOOLEAN even when it's equal to the
|
||||
// default value, so we allow invalid explicit encodings here.
|
||||
inline Result OptionalBoolean(Reader& input, /*out*/ bool& value) {
|
||||
value = false;
|
||||
if (input.Peek(BOOLEAN)) {
|
||||
Result rv = Boolean(input, value);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
// This parser will only parse values between 0..127. If this range is
|
||||
// increased then callers will need to be changed.
|
||||
inline Result Enumerated(Reader& input, uint8_t& value) {
|
||||
return internal::IntegralValue(input, ENUMERATED | 0, value);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
// internal::TimeChoice implements the shared functionality of GeneralizedTime
|
||||
// and TimeChoice. tag must be either UTCTime or GENERALIZED_TIME.
|
||||
//
|
||||
// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
|
||||
// eliminate the chance for complications in converting times to traditional
|
||||
// time formats that start at 1970.
|
||||
Result TimeChoice(Reader& input, uint8_t tag, /*out*/ Time& time);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
|
||||
// eliminate the chance for complications in converting times to traditional
|
||||
// time formats that start at 1970.
|
||||
inline Result GeneralizedTime(Reader& input, /*out*/ Time& time) {
|
||||
return internal::TimeChoice(input, GENERALIZED_TIME, time);
|
||||
}
|
||||
|
||||
// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
|
||||
// eliminate the chance for complications in converting times to traditional
|
||||
// time formats that start at 1970.
|
||||
inline Result TimeChoice(Reader& input, /*out*/ Time& time) {
|
||||
uint8_t expectedTag = input.Peek(UTCTime) ? UTCTime : GENERALIZED_TIME;
|
||||
return internal::TimeChoice(input, expectedTag, time);
|
||||
}
|
||||
|
||||
// Parse a DER integer value into value. Empty values, negative values, and
|
||||
// zero are rejected. If significantBytes is not null, then it will be set to
|
||||
// the number of significant bytes in the value (the length of the value, less
|
||||
// the length of any leading padding), which is useful for key size checks.
|
||||
inline Result PositiveInteger(
|
||||
Reader& input, /*out*/ Input& value,
|
||||
/*optional out*/ Input::size_type* significantBytes = nullptr) {
|
||||
return internal::IntegralBytes(
|
||||
input, INTEGER, internal::IntegralValueRestriction::MustBePositive, value,
|
||||
significantBytes);
|
||||
}
|
||||
|
||||
// This parser will only parse values between 0..127. If this range is
|
||||
// increased then callers will need to be changed.
|
||||
inline Result Integer(Reader& input, /*out*/ uint8_t& value) {
|
||||
return internal::IntegralValue(input, INTEGER, value);
|
||||
}
|
||||
|
||||
// This parser will only parse values between 0..127. If this range is
|
||||
// increased then callers will need to be changed. The default value must be
|
||||
// -1; defaultValue is only a parameter to make it clear in the calling code
|
||||
// what the default value is.
|
||||
inline Result OptionalInteger(Reader& input, long defaultValue,
|
||||
/*out*/ long& value) {
|
||||
// If we need to support a different default value in the future, we need to
|
||||
// test that parsedValue != defaultValue.
|
||||
if (defaultValue != -1) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (!input.Peek(INTEGER)) {
|
||||
value = defaultValue;
|
||||
return Success;
|
||||
}
|
||||
|
||||
uint8_t parsedValue;
|
||||
Result rv = Integer(input, parsedValue);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
value = parsedValue;
|
||||
return Success;
|
||||
}
|
||||
|
||||
inline Result Null(Reader& input) {
|
||||
return ExpectTagAndEmptyValue(input, NULLTag);
|
||||
}
|
||||
|
||||
template <uint8_t Len>
|
||||
Result OID(Reader& input, const uint8_t (&expectedOid)[Len]) {
|
||||
Reader value;
|
||||
Result rv = ExpectTagAndGetValue(input, OIDTag, value);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (!value.MatchRest(expectedOid)) {
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
// PKI-specific types
|
||||
|
||||
inline Result CertificateSerialNumber(Reader& input, /*out*/ Input& value) {
|
||||
// http://tools.ietf.org/html/rfc5280#section-4.1.2.2:
|
||||
//
|
||||
// * "The serial number MUST be a positive integer assigned by the CA to
|
||||
// each certificate."
|
||||
// * "Certificate users MUST be able to handle serialNumber values up to 20
|
||||
// octets. Conforming CAs MUST NOT use serialNumber values longer than 20
|
||||
// octets."
|
||||
// * "Note: Non-conforming CAs may issue certificates with serial numbers
|
||||
// that are negative or zero. Certificate users SHOULD be prepared to
|
||||
// gracefully handle such certificates."
|
||||
return internal::IntegralBytes(
|
||||
input, INTEGER, internal::IntegralValueRestriction::NoRestriction, value);
|
||||
}
|
||||
|
||||
// x.509 and OCSP both use this same version numbering scheme, though OCSP
|
||||
// only supports v1.
|
||||
enum class Version { v1 = 0, v2 = 1, v3 = 2, v4 = 3, Uninitialized = 255 };
|
||||
|
||||
// X.509 Certificate and OCSP ResponseData both use
|
||||
// "[0] EXPLICIT Version DEFAULT v1". Although an explicit encoding of v1 is
|
||||
// illegal, we support it because some real-world OCSP responses explicitly
|
||||
// encode it.
|
||||
Result OptionalVersion(Reader& input, /*out*/ Version& version);
|
||||
|
||||
template <typename ExtensionHandler>
|
||||
inline Result OptionalExtensions(Reader& input, uint8_t tag,
|
||||
ExtensionHandler extensionHandler) {
|
||||
if (!input.Peek(tag)) {
|
||||
return Success;
|
||||
}
|
||||
|
||||
return Nested(input, tag, [extensionHandler](Reader& tagged) {
|
||||
// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
||||
//
|
||||
// TODO(bug 997994): According to the specification, there should never be
|
||||
// an empty sequence of extensions but we've found OCSP responses that have
|
||||
// that (see bug 991898).
|
||||
return NestedOf(
|
||||
tagged, SEQUENCE, SEQUENCE, EmptyAllowed::Yes,
|
||||
[extensionHandler](Reader& extension) -> Result {
|
||||
// Extension ::= SEQUENCE {
|
||||
// extnID OBJECT IDENTIFIER,
|
||||
// critical BOOLEAN DEFAULT FALSE,
|
||||
// extnValue OCTET STRING
|
||||
// }
|
||||
Reader extnID;
|
||||
Result rv = ExpectTagAndGetValue(extension, OIDTag, extnID);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
bool critical;
|
||||
rv = OptionalBoolean(extension, critical);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Input extnValue;
|
||||
rv = ExpectTagAndGetValue(extension, OCTET_STRING, extnValue);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
bool understood = false;
|
||||
rv = extensionHandler(extnID, extnValue, critical, understood);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (critical && !understood) {
|
||||
return Result::ERROR_UNKNOWN_CRITICAL_EXTENSION;
|
||||
}
|
||||
return Success;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Result DigestAlgorithmIdentifier(Reader& input,
|
||||
/*out*/ DigestAlgorithm& algorithm);
|
||||
|
||||
enum class PublicKeyAlgorithm { RSA_PKCS1, ECDSA, Uninitialized };
|
||||
|
||||
Result SignatureAlgorithmIdentifierValue(
|
||||
Reader& input,
|
||||
/*out*/ PublicKeyAlgorithm& publicKeyAlgorithm,
|
||||
/*out*/ DigestAlgorithm& digestAlgorithm);
|
||||
|
||||
struct SignedDataWithSignature final {
|
||||
public:
|
||||
Input data;
|
||||
Input algorithm;
|
||||
Input signature;
|
||||
|
||||
void operator=(const SignedDataWithSignature&) = delete;
|
||||
};
|
||||
|
||||
// Parses a SEQUENCE into tbs and then parses an AlgorithmIdentifier followed
|
||||
// by a BIT STRING into signedData. This handles the commonality between
|
||||
// parsing the signed/signature fields of certificates and OCSP responses. In
|
||||
// the case of an OCSP response, the caller needs to parse the certs
|
||||
// separately.
|
||||
//
|
||||
// Note that signatureAlgorithm is NOT parsed or validated.
|
||||
//
|
||||
// Certificate ::= SEQUENCE {
|
||||
// tbsCertificate TBSCertificate,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signatureValue BIT STRING }
|
||||
//
|
||||
// BasicOCSPResponse ::= SEQUENCE {
|
||||
// tbsResponseData ResponseData,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signature BIT STRING,
|
||||
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
|
||||
Result SignedData(Reader& input, /*out*/ Reader& tbs,
|
||||
/*out*/ SignedDataWithSignature& signedDataWithSignature);
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::pkix::der
|
||||
|
||||
#endif // mozilla_pkix_pkixder_h
|
|
@ -0,0 +1,106 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_pkixnss_h
|
||||
#define mozilla_pkix_pkixnss_h
|
||||
|
||||
#include <seccomon.h>
|
||||
#include "mozpkix/pkixtypes.h"
|
||||
#include "prerror.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
// Verifies the PKCS#1.5 signature on the given data using the given RSA public
|
||||
// key.
|
||||
Result VerifyRSAPKCS1SignedDigestNSS(const SignedDigest& sd,
|
||||
Input subjectPublicKeyInfo,
|
||||
void* pkcs11PinArg);
|
||||
|
||||
// Verifies the ECDSA signature on the given data using the given ECC public
|
||||
// key.
|
||||
Result VerifyECDSASignedDigestNSS(const SignedDigest& sd,
|
||||
Input subjectPublicKeyInfo,
|
||||
void* pkcs11PinArg);
|
||||
|
||||
// Computes the digest of the given data using the given digest algorithm.
|
||||
//
|
||||
// item contains the data to hash.
|
||||
// digestBuf must point to a buffer to where the digest will be written.
|
||||
// digestBufLen must be the size of the buffer, which must be exactly equal
|
||||
// to the size of the digest output (20 for SHA-1, 32 for SHA-256,
|
||||
// etc.)
|
||||
//
|
||||
// TODO: Taking the output buffer as (uint8_t*, size_t) is counter to our
|
||||
// other, extensive, memory safety efforts in mozilla::pkix, and we should find
|
||||
// a way to provide a more-obviously-safe interface.
|
||||
Result DigestBufNSS(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestBufLen);
|
||||
|
||||
Result MapPRErrorCodeToResult(PRErrorCode errorCode);
|
||||
PRErrorCode MapResultToPRErrorCode(Result result);
|
||||
|
||||
// The error codes within each module must fit in 16 bits. We want these
|
||||
// errors to fit in the same module as the NSS errors but not overlap with
|
||||
// any of them. Converting an NSS SEC, NSS SSL, or PSM error to an NS error
|
||||
// involves negating the value of the error and then synthesizing an error
|
||||
// in the NS_ERROR_MODULE_SECURITY module. Hence, PSM errors will start at
|
||||
// a negative value that both doesn't overlap with the current value
|
||||
// ranges for NSS errors and that will fit in 16 bits when negated.
|
||||
static const PRErrorCode ERROR_BASE = -0x4000;
|
||||
static const PRErrorCode ERROR_LIMIT = ERROR_BASE + 1000;
|
||||
|
||||
enum ErrorCode {
|
||||
MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = ERROR_BASE + 0,
|
||||
MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY = ERROR_BASE + 1,
|
||||
MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE = ERROR_BASE + 2,
|
||||
MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA = ERROR_BASE + 3,
|
||||
MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH = ERROR_BASE + 4,
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = ERROR_BASE + 5,
|
||||
MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = ERROR_BASE + 6,
|
||||
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH = ERROR_BASE + 7,
|
||||
MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING = ERROR_BASE + 8,
|
||||
MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG = ERROR_BASE + 9,
|
||||
MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING = ERROR_BASE + 10,
|
||||
MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING = ERROR_BASE + 11,
|
||||
MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME = ERROR_BASE + 12,
|
||||
MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED = ERROR_BASE + 13,
|
||||
MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = ERROR_BASE + 14,
|
||||
MOZILLA_PKIX_ERROR_MITM_DETECTED = ERROR_BASE + 15,
|
||||
END_OF_LIST
|
||||
};
|
||||
|
||||
void RegisterErrorTable();
|
||||
|
||||
inline SECItem UnsafeMapInputToSECItem(Input input) {
|
||||
SECItem result = {siBuffer, const_cast<uint8_t*>(input.UnsafeGetData()),
|
||||
input.GetLength()};
|
||||
static_assert(sizeof(decltype(input.GetLength())) <= sizeof(result.len),
|
||||
"input.GetLength() must fit in a SECItem");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
#endif // mozilla_pkix_pkixnss_h
|
|
@ -0,0 +1,400 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_pkixtypes_h
|
||||
#define mozilla_pkix_pkixtypes_h
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "mozpkix/Input.h"
|
||||
#include "mozpkix/Time.h"
|
||||
#include "stdint.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
enum class DigestAlgorithm {
|
||||
sha512 = 1,
|
||||
sha384 = 2,
|
||||
sha256 = 3,
|
||||
sha1 = 4,
|
||||
};
|
||||
|
||||
enum class NamedCurve {
|
||||
// secp521r1 (OID 1.3.132.0.35, RFC 5480)
|
||||
secp521r1 = 1,
|
||||
|
||||
// secp384r1 (OID 1.3.132.0.34, RFC 5480)
|
||||
secp384r1 = 2,
|
||||
|
||||
// secp256r1 (OID 1.2.840.10045.3.1.7, RFC 5480)
|
||||
secp256r1 = 3,
|
||||
};
|
||||
|
||||
struct SignedDigest final {
|
||||
Input digest;
|
||||
DigestAlgorithm digestAlgorithm;
|
||||
Input signature;
|
||||
|
||||
void operator=(const SignedDigest&) = delete;
|
||||
};
|
||||
|
||||
enum class EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
|
||||
|
||||
enum class KeyUsage : uint8_t {
|
||||
digitalSignature = 0,
|
||||
nonRepudiation = 1,
|
||||
keyEncipherment = 2,
|
||||
dataEncipherment = 3,
|
||||
keyAgreement = 4,
|
||||
keyCertSign = 5,
|
||||
// cRLSign = 6,
|
||||
// encipherOnly = 7,
|
||||
// decipherOnly = 8,
|
||||
noParticularKeyUsageRequired = 0xff,
|
||||
};
|
||||
|
||||
enum class KeyPurposeId {
|
||||
anyExtendedKeyUsage = 0,
|
||||
id_kp_serverAuth = 1, // id-kp-serverAuth
|
||||
id_kp_clientAuth = 2, // id-kp-clientAuth
|
||||
id_kp_codeSigning = 3, // id-kp-codeSigning
|
||||
id_kp_emailProtection = 4, // id-kp-emailProtection
|
||||
id_kp_OCSPSigning = 9, // id-kp-OCSPSigning
|
||||
};
|
||||
|
||||
struct CertPolicyId final {
|
||||
uint16_t numBytes;
|
||||
static const uint16_t MAX_BYTES = 24;
|
||||
uint8_t bytes[MAX_BYTES];
|
||||
|
||||
bool IsAnyPolicy() const;
|
||||
bool operator==(const CertPolicyId& other) const;
|
||||
|
||||
static const CertPolicyId anyPolicy;
|
||||
};
|
||||
|
||||
enum class TrustLevel {
|
||||
TrustAnchor = 1, // certificate is a trusted root CA certificate or
|
||||
// equivalent *for the given policy*.
|
||||
ActivelyDistrusted = 2, // certificate is known to be bad
|
||||
InheritsTrust = 3 // certificate must chain to a trust anchor
|
||||
};
|
||||
|
||||
// Extensions extracted during the verification flow.
|
||||
// See TrustDomain::NoteAuxiliaryExtension.
|
||||
enum class AuxiliaryExtension {
|
||||
// Certificate Transparency data, specifically Signed Certificate
|
||||
// Timestamps (SCTs). See RFC 6962.
|
||||
|
||||
// SCT list embedded in the end entity certificate. Called by BuildCertChain
|
||||
// after the certificate containing the SCTs has passed the revocation checks.
|
||||
EmbeddedSCTList = 1,
|
||||
// SCT list from OCSP response. Called by VerifyEncodedOCSPResponse
|
||||
// when its result is a success and the SCT list is present.
|
||||
SCTListFromOCSPResponse = 2
|
||||
};
|
||||
|
||||
// CertID references the information needed to do revocation checking for the
|
||||
// certificate issued by the given issuer with the given serial number.
|
||||
//
|
||||
// issuer must be the DER-encoded issuer field from the certificate for which
|
||||
// revocation checking is being done, **NOT** the subject field of the issuer
|
||||
// certificate. (Those two fields must be equal to each other, but they may not
|
||||
// be encoded exactly the same, and the encoding matters for OCSP.)
|
||||
// issuerSubjectPublicKeyInfo is the entire DER-encoded subjectPublicKeyInfo
|
||||
// field from the issuer's certificate. serialNumber is the entire DER-encoded
|
||||
// serial number from the subject certificate (the certificate for which we are
|
||||
// checking the revocation status).
|
||||
struct CertID final {
|
||||
public:
|
||||
CertID(Input aIssuer, Input aIssuerSubjectPublicKeyInfo, Input aSerialNumber)
|
||||
: issuer(aIssuer),
|
||||
issuerSubjectPublicKeyInfo(aIssuerSubjectPublicKeyInfo),
|
||||
serialNumber(aSerialNumber) {}
|
||||
const Input issuer;
|
||||
const Input issuerSubjectPublicKeyInfo;
|
||||
const Input serialNumber;
|
||||
|
||||
void operator=(const CertID&) = delete;
|
||||
};
|
||||
typedef std::unique_ptr<CertID> ScopedCertID;
|
||||
|
||||
class DERArray {
|
||||
public:
|
||||
// Returns the number of DER-encoded items in the array.
|
||||
virtual size_t GetLength() const = 0;
|
||||
|
||||
// Returns a weak (non-owning) pointer the ith DER-encoded item in the array
|
||||
// (0-indexed). The result is guaranteed to be non-null if i < GetLength(),
|
||||
// and the result is guaranteed to be nullptr if i >= GetLength().
|
||||
virtual const Input* GetDER(size_t i) const = 0;
|
||||
|
||||
protected:
|
||||
DERArray() {}
|
||||
virtual ~DERArray() {}
|
||||
};
|
||||
|
||||
// Applications control the behavior of path building and verification by
|
||||
// implementing the TrustDomain interface. The TrustDomain is used for all
|
||||
// cryptography and for determining which certificates are trusted or
|
||||
// distrusted.
|
||||
class TrustDomain {
|
||||
public:
|
||||
virtual ~TrustDomain() {}
|
||||
|
||||
// Determine the level of trust in the given certificate for the given role.
|
||||
// This will be called for every certificate encountered during path
|
||||
// building.
|
||||
//
|
||||
// When policy.IsAnyPolicy(), then no policy-related checking should be done.
|
||||
// When !policy.IsAnyPolicy(), then GetCertTrust MUST NOT return with
|
||||
// trustLevel == TrustAnchor unless the given cert is considered a trust
|
||||
// anchor *for that policy*. In particular, if the user has marked an
|
||||
// intermediate certificate as trusted, but that intermediate isn't in the
|
||||
// list of EV roots, then GetCertTrust must result in
|
||||
// trustLevel == InheritsTrust instead of trustLevel == TrustAnchor
|
||||
// (assuming the candidate cert is not actively distrusted).
|
||||
virtual Result GetCertTrust(EndEntityOrCA endEntityOrCA,
|
||||
const CertPolicyId& policy,
|
||||
Input candidateCertDER,
|
||||
/*out*/ TrustLevel& trustLevel) = 0;
|
||||
|
||||
class IssuerChecker {
|
||||
public:
|
||||
// potentialIssuerDER is the complete DER encoding of the certificate to
|
||||
// be checked as a potential issuer.
|
||||
//
|
||||
// If additionalNameConstraints is not nullptr then it must point to an
|
||||
// encoded NameConstraints extension value; in that case, those name
|
||||
// constraints will be checked in addition to any any name constraints
|
||||
// contained in potentialIssuerDER.
|
||||
virtual Result Check(Input potentialIssuerDER,
|
||||
/*optional*/ const Input* additionalNameConstraints,
|
||||
/*out*/ bool& keepGoing) = 0;
|
||||
|
||||
protected:
|
||||
IssuerChecker();
|
||||
virtual ~IssuerChecker();
|
||||
|
||||
IssuerChecker(const IssuerChecker&) = delete;
|
||||
void operator=(const IssuerChecker&) = delete;
|
||||
};
|
||||
|
||||
// Search for a CA certificate with the given name. The implementation must
|
||||
// call checker.Check with the DER encoding of the potential issuer
|
||||
// certificate. The implementation must follow these rules:
|
||||
//
|
||||
// * The implementation must be reentrant and must limit the amount of stack
|
||||
// space it uses; see the note on reentrancy and stack usage below.
|
||||
// * When checker.Check does not return Success then immediately return its
|
||||
// return value.
|
||||
// * When checker.Check returns Success and sets keepGoing = false, then
|
||||
// immediately return Success.
|
||||
// * When checker.Check returns Success and sets keepGoing = true, then
|
||||
// call checker.Check again with a different potential issuer certificate,
|
||||
// if any more are available.
|
||||
// * When no more potential issuer certificates are available, return
|
||||
// Success.
|
||||
// * Don't call checker.Check with the same potential issuer certificate more
|
||||
// than once in a given call of FindIssuer.
|
||||
// * The given time parameter may be used to filter out certificates that are
|
||||
// not valid at the given time, or it may be ignored.
|
||||
//
|
||||
// Note on reentrancy and stack usage: checker.Check will attempt to
|
||||
// recursively build a certificate path from the potential issuer it is given
|
||||
// to a trusted root, as determined by this TrustDomain. That means that
|
||||
// checker.Check may call any/all of the methods on this TrustDomain. In
|
||||
// particular, there will be call stacks that look like this:
|
||||
//
|
||||
// BuildCertChain
|
||||
// [...]
|
||||
// TrustDomain::FindIssuer
|
||||
// [...]
|
||||
// IssuerChecker::Check
|
||||
// [...]
|
||||
// TrustDomain::FindIssuer
|
||||
// [...]
|
||||
// IssuerChecker::Check
|
||||
// [...]
|
||||
//
|
||||
// checker.Check is responsible for limiting the recursion to a reasonable
|
||||
// limit.
|
||||
//
|
||||
// checker.Check will verify that the subject's issuer field matches the
|
||||
// potential issuer's subject field. It will also check that the potential
|
||||
// issuer is valid at the given time. However, if the FindIssuer
|
||||
// implementation has an efficient way of filtering potential issuers by name
|
||||
// and/or validity period itself, then it is probably better for performance
|
||||
// for it to do so.
|
||||
virtual Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker,
|
||||
Time time) = 0;
|
||||
|
||||
// Called as soon as we think we have a valid chain but before revocation
|
||||
// checks are done. This function can be used to compute additional checks,
|
||||
// especially checks that require the entire certificate chain. This callback
|
||||
// can also be used to save a copy of the built certificate chain for later
|
||||
// use.
|
||||
//
|
||||
// This function may be called multiple times, regardless of whether it
|
||||
// returns success or failure. It is guaranteed that BuildCertChain will not
|
||||
// return Success unless the last call to IsChainValid returns Success.
|
||||
// Further,
|
||||
// it is guaranteed that when BuildCertChain returns Success the last chain
|
||||
// passed to IsChainValid is the valid chain that should be used for further
|
||||
// operations that require the whole chain.
|
||||
//
|
||||
// Keep in mind, in particular, that if the application saves a copy of the
|
||||
// certificate chain the last invocation of IsChainValid during a validation,
|
||||
// it is still possible for BuildCertChain to fail, in which case the
|
||||
// application must not assume anything about the validity of the last
|
||||
// certificate chain passed to IsChainValid; especially, it would be very
|
||||
// wrong to assume that the certificate chain is valid.
|
||||
//
|
||||
// certChain.GetDER(0) is the trust anchor.
|
||||
virtual Result IsChainValid(const DERArray& certChain, Time time,
|
||||
const CertPolicyId& requiredPolicy) = 0;
|
||||
|
||||
virtual Result CheckRevocation(EndEntityOrCA endEntityOrCA,
|
||||
const CertID& certID, Time time,
|
||||
Duration validityDuration,
|
||||
/*optional*/ const Input* stapledOCSPresponse,
|
||||
/*optional*/ const Input* aiaExtension) = 0;
|
||||
|
||||
// Check that the given digest algorithm is acceptable for use in signatures.
|
||||
//
|
||||
// Return Success if the algorithm is acceptable,
|
||||
// Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED if the algorithm is not
|
||||
// acceptable, or another error code if another error occurred.
|
||||
virtual Result CheckSignatureDigestAlgorithm(DigestAlgorithm digestAlg,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
Time notBefore) = 0;
|
||||
|
||||
// Check that the RSA public key size is acceptable.
|
||||
//
|
||||
// Return Success if the key size is acceptable,
|
||||
// Result::ERROR_INADEQUATE_KEY_SIZE if the key size is not acceptable,
|
||||
// or another error code if another error occurred.
|
||||
virtual Result CheckRSAPublicKeyModulusSizeInBits(
|
||||
EndEntityOrCA endEntityOrCA, unsigned int modulusSizeInBits) = 0;
|
||||
|
||||
// Verify the given RSA PKCS#1.5 signature on the given digest using the
|
||||
// given RSA public key.
|
||||
//
|
||||
// CheckRSAPublicKeyModulusSizeInBits will be called before calling this
|
||||
// function, so it is not necessary to repeat those checks here. However,
|
||||
// VerifyRSAPKCS1SignedDigest *is* responsible for doing the mathematical
|
||||
// verification of the public key validity as specified in NIST SP 800-56A.
|
||||
virtual Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) = 0;
|
||||
|
||||
// Check that the given named ECC curve is acceptable for ECDSA signatures.
|
||||
//
|
||||
// Return Success if the curve is acceptable,
|
||||
// Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE if the curve is not acceptable,
|
||||
// or another error code if another error occurred.
|
||||
virtual Result CheckECDSACurveIsAcceptable(EndEntityOrCA endEntityOrCA,
|
||||
NamedCurve curve) = 0;
|
||||
|
||||
// Verify the given ECDSA signature on the given digest using the given ECC
|
||||
// public key.
|
||||
//
|
||||
// CheckECDSACurveIsAcceptable will be called before calling this function,
|
||||
// so it is not necessary to repeat that check here. However,
|
||||
// VerifyECDSASignedDigest *is* responsible for doing the mathematical
|
||||
// verification of the public key validity as specified in NIST SP 800-56A.
|
||||
virtual Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) = 0;
|
||||
|
||||
// Check that the validity duration is acceptable.
|
||||
//
|
||||
// Return Success if the validity duration is acceptable,
|
||||
// Result::ERROR_VALIDITY_TOO_LONG if the validity duration is not acceptable,
|
||||
// or another error code if another error occurred.
|
||||
virtual Result CheckValidityIsAcceptable(Time notBefore, Time notAfter,
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
KeyPurposeId keyPurpose) = 0;
|
||||
|
||||
// For compatibility, a CA certificate with an extended key usage that
|
||||
// contains the id-Netscape-stepUp OID but does not contain the
|
||||
// id-kp-serverAuth OID may be considered valid for issuing server auth
|
||||
// certificates. This function allows TrustDomain implementations to control
|
||||
// this setting based on the start of the validity period of the certificate
|
||||
// in question.
|
||||
virtual Result NetscapeStepUpMatchesServerAuth(Time notBefore,
|
||||
/*out*/ bool& matches) = 0;
|
||||
|
||||
// Some certificate or OCSP response extensions do not directly participate
|
||||
// in the verification flow, but might still be of interest to the clients
|
||||
// (notably Certificate Transparency data, RFC 6962). Such extensions are
|
||||
// extracted and passed to this function for further processing.
|
||||
virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
|
||||
Input extensionData) = 0;
|
||||
|
||||
// Compute a digest of the data in item using the given digest algorithm.
|
||||
//
|
||||
// item contains the data to hash.
|
||||
// digestBuf points to a buffer to where the digest will be written.
|
||||
// digestBufLen will be the size of the digest output (20 for SHA-1,
|
||||
// 32 for SHA-256, etc.).
|
||||
//
|
||||
// TODO: Taking the output buffer as (uint8_t*, size_t) is counter to our
|
||||
// other, extensive, memory safety efforts in mozilla::pkix, and we should
|
||||
// find a way to provide a more-obviously-safe interface.
|
||||
virtual Result DigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestBufLen) = 0;
|
||||
|
||||
protected:
|
||||
TrustDomain() {}
|
||||
|
||||
TrustDomain(const TrustDomain&) = delete;
|
||||
void operator=(const TrustDomain&) = delete;
|
||||
};
|
||||
|
||||
enum class FallBackToSearchWithinSubject { No = 0, Yes = 1 };
|
||||
|
||||
// Applications control the behavior of matching presented name information from
|
||||
// a certificate against a reference hostname by implementing the
|
||||
// NameMatchingPolicy interface. Used in concert with CheckCertHostname.
|
||||
class NameMatchingPolicy {
|
||||
public:
|
||||
virtual ~NameMatchingPolicy() {}
|
||||
|
||||
// Given that the certificate in question has a notBefore field with the given
|
||||
// value, should name matching fall back to searching within the subject
|
||||
// common name field?
|
||||
virtual Result FallBackToCommonName(
|
||||
Time notBefore,
|
||||
/*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) = 0;
|
||||
|
||||
protected:
|
||||
NameMatchingPolicy() {}
|
||||
|
||||
NameMatchingPolicy(const NameMatchingPolicy&) = delete;
|
||||
void operator=(const NameMatchingPolicy&) = delete;
|
||||
};
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
#endif // mozilla_pkix_pkixtypes_h
|
|
@ -0,0 +1,265 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_pkix_pkixutil_h
|
||||
#define mozilla_pkix_pkixutil_h
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace pkix {
|
||||
|
||||
// During path building and verification, we build a linked list of BackCerts
|
||||
// from the current cert toward the end-entity certificate. The linked list
|
||||
// is used to verify properties that aren't local to the current certificate
|
||||
// and/or the direct link between the current certificate and its issuer,
|
||||
// such as name constraints.
|
||||
//
|
||||
// Each BackCert contains pointers to all the given certificate's extensions
|
||||
// so that we can parse the extension block once and then process the
|
||||
// extensions in an order that may be different than they appear in the cert.
|
||||
class BackCert final {
|
||||
public:
|
||||
// certDER and childCert must be valid for the lifetime of BackCert.
|
||||
BackCert(Input aCertDER, EndEntityOrCA aEndEntityOrCA,
|
||||
const BackCert* aChildCert)
|
||||
: der(aCertDER),
|
||||
endEntityOrCA(aEndEntityOrCA),
|
||||
childCert(aChildCert),
|
||||
version(der::Version::Uninitialized) {}
|
||||
|
||||
Result Init();
|
||||
|
||||
const Input GetDER() const { return der; }
|
||||
const der::SignedDataWithSignature& GetSignedData() const {
|
||||
return signedData;
|
||||
}
|
||||
|
||||
der::Version GetVersion() const { return version; }
|
||||
const Input GetSerialNumber() const { return serialNumber; }
|
||||
const Input GetSignature() const { return signature; }
|
||||
const Input GetIssuer() const { return issuer; }
|
||||
// XXX: "validity" is a horrible name for the structure that holds
|
||||
// notBefore & notAfter, but that is the name used in RFC 5280 and we use the
|
||||
// RFC 5280 names for everything.
|
||||
const Input GetValidity() const { return validity; }
|
||||
const Input GetSubject() const { return subject; }
|
||||
const Input GetSubjectPublicKeyInfo() const { return subjectPublicKeyInfo; }
|
||||
const Input* GetAuthorityInfoAccess() const {
|
||||
return MaybeInput(authorityInfoAccess);
|
||||
}
|
||||
const Input* GetBasicConstraints() const {
|
||||
return MaybeInput(basicConstraints);
|
||||
}
|
||||
const Input* GetCertificatePolicies() const {
|
||||
return MaybeInput(certificatePolicies);
|
||||
}
|
||||
const Input* GetExtKeyUsage() const { return MaybeInput(extKeyUsage); }
|
||||
const Input* GetKeyUsage() const { return MaybeInput(keyUsage); }
|
||||
const Input* GetInhibitAnyPolicy() const {
|
||||
return MaybeInput(inhibitAnyPolicy);
|
||||
}
|
||||
const Input* GetNameConstraints() const {
|
||||
return MaybeInput(nameConstraints);
|
||||
}
|
||||
const Input* GetSubjectAltName() const { return MaybeInput(subjectAltName); }
|
||||
const Input* GetRequiredTLSFeatures() const {
|
||||
return MaybeInput(requiredTLSFeatures);
|
||||
}
|
||||
const Input* GetSignedCertificateTimestamps() const {
|
||||
return MaybeInput(signedCertificateTimestamps);
|
||||
}
|
||||
|
||||
private:
|
||||
const Input der;
|
||||
|
||||
public:
|
||||
const EndEntityOrCA endEntityOrCA;
|
||||
BackCert const* const childCert;
|
||||
|
||||
private:
|
||||
// When parsing certificates in BackCert::Init, we don't accept empty
|
||||
// extensions. Consequently, we don't have to store a distinction between
|
||||
// empty extensions and extensions that weren't included. However, when
|
||||
// *processing* extensions, we distinguish between whether an extension was
|
||||
// included or not based on whetehr the GetXXX function for the extension
|
||||
// returns nullptr.
|
||||
static inline const Input* MaybeInput(const Input& item) {
|
||||
return item.GetLength() > 0 ? &item : nullptr;
|
||||
}
|
||||
|
||||
der::SignedDataWithSignature signedData;
|
||||
|
||||
der::Version version;
|
||||
Input serialNumber;
|
||||
Input signature;
|
||||
Input issuer;
|
||||
// XXX: "validity" is a horrible name for the structure that holds
|
||||
// notBefore & notAfter, but that is the name used in RFC 5280 and we use the
|
||||
// RFC 5280 names for everything.
|
||||
Input validity;
|
||||
Input subject;
|
||||
Input subjectPublicKeyInfo;
|
||||
|
||||
Input authorityInfoAccess;
|
||||
Input basicConstraints;
|
||||
Input certificatePolicies;
|
||||
Input extKeyUsage;
|
||||
Input inhibitAnyPolicy;
|
||||
Input keyUsage;
|
||||
Input nameConstraints;
|
||||
Input subjectAltName;
|
||||
Input criticalNetscapeCertificateType;
|
||||
Input requiredTLSFeatures;
|
||||
Input signedCertificateTimestamps; // RFC 6962 (Certificate Transparency)
|
||||
|
||||
Result RememberExtension(Reader& extnID, Input extnValue, bool critical,
|
||||
/*out*/ bool& understood);
|
||||
|
||||
BackCert(const BackCert&) = delete;
|
||||
void operator=(const BackCert&) = delete;
|
||||
};
|
||||
|
||||
class NonOwningDERArray final : public DERArray {
|
||||
public:
|
||||
NonOwningDERArray() : numItems(0) {
|
||||
// we don't need to initialize the items array because we always check
|
||||
// numItems before accessing i.
|
||||
}
|
||||
|
||||
size_t GetLength() const override { return numItems; }
|
||||
|
||||
const Input* GetDER(size_t i) const override {
|
||||
return i < numItems ? &items[i] : nullptr;
|
||||
}
|
||||
|
||||
Result Append(Input der) {
|
||||
if (numItems >= MAX_LENGTH) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
Result rv = items[numItems].Init(der); // structure assignment
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
++numItems;
|
||||
return Success;
|
||||
}
|
||||
|
||||
// Public so we can static_assert on this. Keep in sync with MAX_SUBCA_COUNT.
|
||||
static const size_t MAX_LENGTH = 8;
|
||||
|
||||
private:
|
||||
Input items[MAX_LENGTH]; // avoids any heap allocations
|
||||
size_t numItems;
|
||||
|
||||
NonOwningDERArray(const NonOwningDERArray&) = delete;
|
||||
void operator=(const NonOwningDERArray&) = delete;
|
||||
};
|
||||
|
||||
// Extracts the SignedCertificateTimestampList structure which is encoded as an
|
||||
// OCTET STRING within the X.509v3 / OCSP extensions (see RFC 6962 section 3.3).
|
||||
Result ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
|
||||
Input& sctList);
|
||||
|
||||
inline unsigned int DaysBeforeYear(unsigned int year) {
|
||||
assert(year <= 9999);
|
||||
return ((year - 1u) * 365u) +
|
||||
((year - 1u) / 4u) // leap years are every 4 years,
|
||||
- ((year - 1u) / 100u) // except years divisible by 100,
|
||||
+ ((year - 1u) / 400u); // except years divisible by 400.
|
||||
}
|
||||
|
||||
static const size_t MAX_DIGEST_SIZE_IN_BYTES = 512 / 8; // sha-512
|
||||
|
||||
Result DigestSignedData(TrustDomain& trustDomain,
|
||||
const der::SignedDataWithSignature& signedData,
|
||||
/*out*/ uint8_t (&digestBuf)[MAX_DIGEST_SIZE_IN_BYTES],
|
||||
/*out*/ der::PublicKeyAlgorithm& publicKeyAlg,
|
||||
/*out*/ SignedDigest& signedDigest);
|
||||
|
||||
Result VerifySignedDigest(TrustDomain& trustDomain,
|
||||
der::PublicKeyAlgorithm publicKeyAlg,
|
||||
const SignedDigest& signedDigest,
|
||||
Input signerSubjectPublicKeyInfo);
|
||||
|
||||
// Combines DigestSignedData and VerifySignedDigest
|
||||
Result VerifySignedData(TrustDomain& trustDomain,
|
||||
const der::SignedDataWithSignature& signedData,
|
||||
Input signerSubjectPublicKeyInfo);
|
||||
|
||||
// Extracts the key parameters from |subjectPublicKeyInfo|, invoking
|
||||
// the relevant methods of |trustDomain|.
|
||||
Result CheckSubjectPublicKeyInfo(Input subjectPublicKeyInfo,
|
||||
TrustDomain& trustDomain,
|
||||
EndEntityOrCA endEntityOrCA);
|
||||
|
||||
// In a switch over an enum, sometimes some compilers are not satisfied that
|
||||
// all control flow paths have been considered unless there is a default case.
|
||||
// However, in our code, such a default case is almost always unreachable dead
|
||||
// code. That can be particularly problematic when the compiler wants the code
|
||||
// to choose a value, such as a return value, for the default case, but there's
|
||||
// no appropriate "impossible case" value to choose.
|
||||
//
|
||||
// MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM accounts for this. Example:
|
||||
//
|
||||
// // In xy.cpp
|
||||
// #include "xt.h"
|
||||
//
|
||||
// enum class XY { X, Y };
|
||||
//
|
||||
// int func(XY xy) {
|
||||
// switch (xy) {
|
||||
// case XY::X: return 1;
|
||||
// case XY::Y; return 2;
|
||||
// MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
// }
|
||||
// }
|
||||
#if defined(__clang__)
|
||||
// Clang will warn if not all cases are covered (-Wswitch-enum) AND it will
|
||||
// warn if a switch statement that covers every enum label has a default case
|
||||
// (-W-covered-switch-default). Versions prior to 3.5 warned about unreachable
|
||||
// code in such default cases (-Wunreachable-code) even when
|
||||
// -W-covered-switch-default was disabled, but that changed in Clang 3.5.
|
||||
#define MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM // empty
|
||||
#elif defined(__GNUC__)
|
||||
// GCC will warn if not all cases are covered (-Wswitch-enum). It does not
|
||||
// assume that the default case is unreachable.
|
||||
#define MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM \
|
||||
default: \
|
||||
assert(false); \
|
||||
__builtin_unreachable();
|
||||
#elif defined(_MSC_VER)
|
||||
// MSVC will warn if not all cases are covered (C4061, level 4). It does not
|
||||
// assume that the default case is unreachable.
|
||||
#define MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM \
|
||||
default: \
|
||||
assert(false); \
|
||||
__assume(0);
|
||||
#else
|
||||
#error Unsupported compiler for MOZILLA_PKIX_UNREACHABLE_DEFAULT.
|
||||
#endif
|
||||
}
|
||||
} // namespace mozilla::pkix
|
||||
|
||||
#endif // mozilla_pkix_pkixutil_h
|
|
@ -0,0 +1,418 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/pkix.h"
|
||||
|
||||
#include "mozpkix/pkixcheck.h"
|
||||
#include "mozpkix/pkixutil.h"
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
static Result BuildForward(TrustDomain& trustDomain,
|
||||
const BackCert& subject,
|
||||
Time time,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
/*optional*/ const Input* stapledOCSPResponse,
|
||||
unsigned int subCACount,
|
||||
unsigned int& buildForwardCallBudget);
|
||||
|
||||
TrustDomain::IssuerChecker::IssuerChecker() { }
|
||||
TrustDomain::IssuerChecker::~IssuerChecker() { }
|
||||
|
||||
// The implementation of TrustDomain::IssuerTracker is in a subclass only to
|
||||
// hide the implementation from external users.
|
||||
class PathBuildingStep final : public TrustDomain::IssuerChecker
|
||||
{
|
||||
public:
|
||||
PathBuildingStep(TrustDomain& aTrustDomain, const BackCert& aSubject,
|
||||
Time aTime, KeyPurposeId aRequiredEKUIfPresent,
|
||||
const CertPolicyId& aRequiredPolicy,
|
||||
/*optional*/ const Input* aStapledOCSPResponse,
|
||||
unsigned int aSubCACount, Result aDeferredSubjectError,
|
||||
unsigned int& aBuildForwardCallBudget)
|
||||
: trustDomain(aTrustDomain)
|
||||
, subject(aSubject)
|
||||
, time(aTime)
|
||||
, requiredEKUIfPresent(aRequiredEKUIfPresent)
|
||||
, requiredPolicy(aRequiredPolicy)
|
||||
, stapledOCSPResponse(aStapledOCSPResponse)
|
||||
, subCACount(aSubCACount)
|
||||
, deferredSubjectError(aDeferredSubjectError)
|
||||
, subjectSignaturePublicKeyAlg(der::PublicKeyAlgorithm::Uninitialized)
|
||||
, result(Result::FATAL_ERROR_LIBRARY_FAILURE)
|
||||
, resultWasSet(false)
|
||||
, buildForwardCallBudget(aBuildForwardCallBudget)
|
||||
{
|
||||
}
|
||||
|
||||
Result Check(Input potentialIssuerDER,
|
||||
/*optional*/ const Input* additionalNameConstraints,
|
||||
/*out*/ bool& keepGoing) override;
|
||||
|
||||
Result CheckResult() const;
|
||||
|
||||
private:
|
||||
TrustDomain& trustDomain;
|
||||
const BackCert& subject;
|
||||
const Time time;
|
||||
const KeyPurposeId requiredEKUIfPresent;
|
||||
const CertPolicyId& requiredPolicy;
|
||||
/*optional*/ Input const* const stapledOCSPResponse;
|
||||
const unsigned int subCACount;
|
||||
const Result deferredSubjectError;
|
||||
|
||||
// Initialized lazily.
|
||||
uint8_t subjectSignatureDigestBuf[MAX_DIGEST_SIZE_IN_BYTES];
|
||||
der::PublicKeyAlgorithm subjectSignaturePublicKeyAlg;
|
||||
SignedDigest subjectSignature;
|
||||
|
||||
Result RecordResult(Result currentResult, /*out*/ bool& keepGoing);
|
||||
Result result;
|
||||
bool resultWasSet;
|
||||
unsigned int& buildForwardCallBudget;
|
||||
|
||||
PathBuildingStep(const PathBuildingStep&) = delete;
|
||||
void operator=(const PathBuildingStep&) = delete;
|
||||
};
|
||||
|
||||
Result
|
||||
PathBuildingStep::RecordResult(Result newResult, /*out*/ bool& keepGoing)
|
||||
{
|
||||
if (newResult == Result::ERROR_UNTRUSTED_CERT) {
|
||||
newResult = Result::ERROR_UNTRUSTED_ISSUER;
|
||||
} else if (newResult == Result::ERROR_EXPIRED_CERTIFICATE) {
|
||||
newResult = Result::ERROR_EXPIRED_ISSUER_CERTIFICATE;
|
||||
} else if (newResult == Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
|
||||
newResult = Result::ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE;
|
||||
}
|
||||
|
||||
if (resultWasSet) {
|
||||
if (result == Success) {
|
||||
return NotReached("RecordResult called after finding a chain",
|
||||
Result::FATAL_ERROR_INVALID_STATE);
|
||||
}
|
||||
// If every potential issuer has the same problem (e.g. expired) and/or if
|
||||
// there is only one bad potential issuer, then return a more specific
|
||||
// error. Otherwise, punt on trying to decide which error should be
|
||||
// returned by returning the generic Result::ERROR_UNKNOWN_ISSUER error.
|
||||
if (newResult != Success && newResult != result) {
|
||||
newResult = Result::ERROR_UNKNOWN_ISSUER;
|
||||
}
|
||||
}
|
||||
|
||||
result = newResult;
|
||||
resultWasSet = true;
|
||||
keepGoing = result != Success;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
PathBuildingStep::CheckResult() const
|
||||
{
|
||||
if (!resultWasSet) {
|
||||
return Result::ERROR_UNKNOWN_ISSUER;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// The code that executes in the inner loop of BuildForward
|
||||
Result
|
||||
PathBuildingStep::Check(Input potentialIssuerDER,
|
||||
/*optional*/ const Input* additionalNameConstraints,
|
||||
/*out*/ bool& keepGoing)
|
||||
{
|
||||
BackCert potentialIssuer(potentialIssuerDER, EndEntityOrCA::MustBeCA,
|
||||
&subject);
|
||||
Result rv = potentialIssuer.Init();
|
||||
if (rv != Success) {
|
||||
return RecordResult(rv, keepGoing);
|
||||
}
|
||||
|
||||
// Simple TrustDomain::FindIssuers implementations may pass in all possible
|
||||
// CA certificates without any filtering. Because of this, we don't consider
|
||||
// a mismatched name to be an error. Instead, we just pretend that any
|
||||
// certificate without a matching name was never passed to us. In particular,
|
||||
// we treat the case where the TrustDomain only asks us to check CA
|
||||
// certificates with mismatched names as equivalent to the case where the
|
||||
// TrustDomain never called Check() at all.
|
||||
if (!InputsAreEqual(potentialIssuer.GetSubject(), subject.GetIssuer())) {
|
||||
keepGoing = true;
|
||||
return Success;
|
||||
}
|
||||
|
||||
// Loop prevention, done as recommended by RFC4158 Section 5.2
|
||||
// TODO: this doesn't account for subjectAltNames!
|
||||
// TODO(perf): This probably can and should be optimized in some way.
|
||||
for (const BackCert* prev = potentialIssuer.childCert; prev;
|
||||
prev = prev->childCert) {
|
||||
if (InputsAreEqual(potentialIssuer.GetSubjectPublicKeyInfo(),
|
||||
prev->GetSubjectPublicKeyInfo()) &&
|
||||
InputsAreEqual(potentialIssuer.GetSubject(), prev->GetSubject())) {
|
||||
// XXX: error code
|
||||
return RecordResult(Result::ERROR_UNKNOWN_ISSUER, keepGoing);
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialIssuer.GetNameConstraints()) {
|
||||
rv = CheckNameConstraints(*potentialIssuer.GetNameConstraints(),
|
||||
subject, requiredEKUIfPresent);
|
||||
if (rv != Success) {
|
||||
return RecordResult(rv, keepGoing);
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalNameConstraints) {
|
||||
rv = CheckNameConstraints(*additionalNameConstraints, subject,
|
||||
requiredEKUIfPresent);
|
||||
if (rv != Success) {
|
||||
return RecordResult(rv, keepGoing);
|
||||
}
|
||||
}
|
||||
|
||||
rv = CheckTLSFeatures(subject, potentialIssuer);
|
||||
if (rv != Success) {
|
||||
return RecordResult(rv, keepGoing);
|
||||
}
|
||||
|
||||
// If we've ran out of budget, stop searching.
|
||||
if (buildForwardCallBudget == 0) {
|
||||
Result savedRv = RecordResult(Result::ERROR_UNKNOWN_ISSUER, keepGoing);
|
||||
keepGoing = false;
|
||||
return savedRv;
|
||||
}
|
||||
buildForwardCallBudget--;
|
||||
|
||||
// RFC 5280, Section 4.2.1.3: "If the keyUsage extension is present, then the
|
||||
// subject public key MUST NOT be used to verify signatures on certificates
|
||||
// or CRLs unless the corresponding keyCertSign or cRLSign bit is set."
|
||||
rv = BuildForward(trustDomain, potentialIssuer, time, KeyUsage::keyCertSign,
|
||||
requiredEKUIfPresent, requiredPolicy, nullptr, subCACount,
|
||||
buildForwardCallBudget);
|
||||
if (rv != Success) {
|
||||
return RecordResult(rv, keepGoing);
|
||||
}
|
||||
|
||||
// Calculate the digest of the subject's signed data if we haven't already
|
||||
// done so. We do this lazily to avoid doing it at all if we backtrack before
|
||||
// getting to this point. We cache the result to avoid recalculating it if we
|
||||
// backtrack after getting to this point.
|
||||
if (subjectSignature.digest.GetLength() == 0) {
|
||||
rv = DigestSignedData(trustDomain, subject.GetSignedData(),
|
||||
subjectSignatureDigestBuf,
|
||||
subjectSignaturePublicKeyAlg, subjectSignature);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
rv = VerifySignedDigest(trustDomain, subjectSignaturePublicKeyAlg,
|
||||
subjectSignature,
|
||||
potentialIssuer.GetSubjectPublicKeyInfo());
|
||||
if (rv != Success) {
|
||||
return RecordResult(rv, keepGoing);
|
||||
}
|
||||
|
||||
// We avoid doing revocation checking for expired certificates because OCSP
|
||||
// responders are allowed to forget about expired certificates, and many OCSP
|
||||
// responders return an error when asked for the status of an expired
|
||||
// certificate.
|
||||
if (deferredSubjectError != Result::ERROR_EXPIRED_CERTIFICATE) {
|
||||
CertID certID(subject.GetIssuer(), potentialIssuer.GetSubjectPublicKeyInfo(),
|
||||
subject.GetSerialNumber());
|
||||
Time notBefore(Time::uninitialized);
|
||||
Time notAfter(Time::uninitialized);
|
||||
// This should never fail. If we're here, we've already parsed the validity
|
||||
// and checked that the given time is in the certificate's validity period.
|
||||
rv = ParseValidity(subject.GetValidity(), ¬Before, ¬After);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Duration validityDuration(notAfter, notBefore);
|
||||
rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time,
|
||||
validityDuration, stapledOCSPResponse,
|
||||
subject.GetAuthorityInfoAccess());
|
||||
if (rv != Success) {
|
||||
// Since this is actually a problem with the current subject certificate
|
||||
// (rather than the issuer), it doesn't make sense to keep going; all
|
||||
// paths through this certificate will fail.
|
||||
Result savedRv = RecordResult(rv, keepGoing);
|
||||
keepGoing = false;
|
||||
return savedRv;
|
||||
}
|
||||
|
||||
if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
|
||||
const Input* sctExtension = subject.GetSignedCertificateTimestamps();
|
||||
if (sctExtension) {
|
||||
Input sctList;
|
||||
rv = ExtractSignedCertificateTimestampListFromExtension(*sctExtension,
|
||||
sctList);
|
||||
if (rv != Success) {
|
||||
// Again, the problem is with this certificate, and all paths through
|
||||
// it will fail.
|
||||
Result savedRv = RecordResult(rv, keepGoing);
|
||||
keepGoing = false;
|
||||
return savedRv;
|
||||
}
|
||||
trustDomain.NoteAuxiliaryExtension(AuxiliaryExtension::EmbeddedSCTList,
|
||||
sctList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RecordResult(Success, keepGoing);
|
||||
}
|
||||
|
||||
// Recursively build the path from the given subject certificate to the root.
|
||||
//
|
||||
// Be very careful about changing the order of checks. The order is significant
|
||||
// because it affects which error we return when a certificate or certificate
|
||||
// chain has multiple problems. See the error ranking documentation in
|
||||
// pkix/pkix.h.
|
||||
static Result
|
||||
BuildForward(TrustDomain& trustDomain,
|
||||
const BackCert& subject,
|
||||
Time time,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
/*optional*/ const Input* stapledOCSPResponse,
|
||||
unsigned int subCACount,
|
||||
unsigned int& buildForwardCallBudget)
|
||||
{
|
||||
Result rv;
|
||||
|
||||
TrustLevel trustLevel;
|
||||
// If this is an end-entity and not a trust anchor, we defer reporting
|
||||
// any error found here until after attempting to find a valid chain.
|
||||
// See the explanation of error prioritization in pkix.h.
|
||||
rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
|
||||
requiredKeyUsageIfPresent,
|
||||
requiredEKUIfPresent, requiredPolicy,
|
||||
subCACount, trustLevel);
|
||||
Result deferredEndEntityError = Success;
|
||||
if (rv != Success) {
|
||||
if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
|
||||
trustLevel != TrustLevel::TrustAnchor) {
|
||||
deferredEndEntityError = rv;
|
||||
} else {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
if (trustLevel == TrustLevel::TrustAnchor) {
|
||||
// End of the recursion.
|
||||
|
||||
NonOwningDERArray chain;
|
||||
for (const BackCert* cert = &subject; cert; cert = cert->childCert) {
|
||||
rv = chain.Append(cert->GetDER());
|
||||
if (rv != Success) {
|
||||
return NotReached("NonOwningDERArray::SetItem failed.", rv);
|
||||
}
|
||||
}
|
||||
|
||||
// This must be done here, after the chain is built but before any
|
||||
// revocation checks have been done.
|
||||
return trustDomain.IsChainValid(chain, time, requiredPolicy);
|
||||
}
|
||||
|
||||
if (subject.endEntityOrCA == EndEntityOrCA::MustBeCA) {
|
||||
// Avoid stack overflows and poor performance by limiting cert chain
|
||||
// length.
|
||||
static const unsigned int MAX_SUBCA_COUNT = 6;
|
||||
static_assert(1/*end-entity*/ + MAX_SUBCA_COUNT + 1/*root*/ ==
|
||||
NonOwningDERArray::MAX_LENGTH,
|
||||
"MAX_SUBCA_COUNT and NonOwningDERArray::MAX_LENGTH mismatch.");
|
||||
if (subCACount >= MAX_SUBCA_COUNT) {
|
||||
return Result::ERROR_UNKNOWN_ISSUER;
|
||||
}
|
||||
++subCACount;
|
||||
} else {
|
||||
assert(subCACount == 0);
|
||||
}
|
||||
|
||||
// Find a trusted issuer.
|
||||
|
||||
PathBuildingStep pathBuilder(trustDomain, subject, time,
|
||||
requiredEKUIfPresent, requiredPolicy,
|
||||
stapledOCSPResponse, subCACount,
|
||||
deferredEndEntityError, buildForwardCallBudget);
|
||||
|
||||
// TODO(bug 965136): Add SKI/AKI matching optimizations
|
||||
rv = trustDomain.FindIssuer(subject.GetIssuer(), pathBuilder, time);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = pathBuilder.CheckResult();
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// If we found a valid chain but deferred reporting an error with the
|
||||
// end-entity certificate, report it now.
|
||||
if (deferredEndEntityError != Success) {
|
||||
return deferredEndEntityError;
|
||||
}
|
||||
|
||||
// We've built a valid chain from the subject cert up to a trusted root.
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
BuildCertChain(TrustDomain& trustDomain, Input certDER,
|
||||
Time time, EndEntityOrCA endEntityOrCA,
|
||||
KeyUsage requiredKeyUsageIfPresent,
|
||||
KeyPurposeId requiredEKUIfPresent,
|
||||
const CertPolicyId& requiredPolicy,
|
||||
/*optional*/ const Input* stapledOCSPResponse)
|
||||
{
|
||||
// XXX: Support the legacy use of the subject CN field for indicating the
|
||||
// domain name the certificate is valid for.
|
||||
BackCert cert(certDER, endEntityOrCA, nullptr);
|
||||
Result rv = cert.Init();
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// See bug 1056341 for context. If mozilla::pkix is being used in an
|
||||
// environment where there are many certificates that all have the same
|
||||
// distinguished name as their subject and issuer (but different SPKIs - see
|
||||
// the loop prevention as per RFC4158 Section 5.2 in PathBuildingStep::Check),
|
||||
// the space to search becomes exponential. Because it would be prohibitively
|
||||
// expensive to explore the entire space, we introduce a budget here that,
|
||||
// when exhausted, terminates the search with the result
|
||||
// Result::ERROR_UNKNOWN_ISSUER. Essentially, we limit the total number of
|
||||
// times `BuildForward` can be called. The current value appears to be a good
|
||||
// balance between finding a path when one exists (when the space isn't too
|
||||
// large) and timing out quickly enough when the space is too large or there
|
||||
// is no valid path to a trust anchor.
|
||||
unsigned int buildForwardCallBudget = 200000;
|
||||
return BuildForward(trustDomain, cert, time, requiredKeyUsageIfPresent,
|
||||
requiredEKUIfPresent, requiredPolicy, stapledOCSPResponse,
|
||||
0/*subCACount*/, buildForwardCallBudget);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::pkix
|
|
@ -0,0 +1,323 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2014 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/pkixutil.h"
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
Result
|
||||
BackCert::Init()
|
||||
{
|
||||
Result rv;
|
||||
|
||||
// Certificate ::= SEQUENCE {
|
||||
// tbsCertificate TBSCertificate,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signatureValue BIT STRING }
|
||||
|
||||
Reader tbsCertificate;
|
||||
|
||||
// The scope of |input| and |certificate| are limited to this block so we
|
||||
// don't accidentally confuse them for tbsCertificate later.
|
||||
{
|
||||
Reader certificate;
|
||||
rv = der::ExpectTagAndGetValueAtEnd(der, der::SEQUENCE, certificate);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::SignedData(certificate, tbsCertificate, signedData);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::End(certificate);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// serialNumber CertificateSerialNumber,
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// validity Validity,
|
||||
// subject Name,
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// extensions [3] EXPLICIT Extensions OPTIONAL
|
||||
// -- If present, version MUST be v3
|
||||
// }
|
||||
rv = der::OptionalVersion(tbsCertificate, version);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::CertificateSerialNumber(tbsCertificate, serialNumber);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, signature);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE, issuer);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, validity);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
// TODO(bug XXXXXXX): We rely on the the caller of mozilla::pkix to validate
|
||||
// that the name is syntactically valid, if they care. In Gecko we do this
|
||||
// implicitly by parsing the certificate into a CERTCertificate object.
|
||||
// Instead of relying on the caller to do this, we should do it ourselves.
|
||||
rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE, subject);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE,
|
||||
subjectPublicKeyInfo);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static const uint8_t CSC = der::CONTEXT_SPECIFIC | der::CONSTRUCTED;
|
||||
|
||||
// According to RFC 5280, all fields below this line are forbidden for
|
||||
// certificate versions less than v3. However, for compatibility reasons,
|
||||
// we parse v1/v2 certificates in the same way as v3 certificates. So if
|
||||
// these fields appear in a v1 certificate, they will be used.
|
||||
|
||||
// Ignore issuerUniqueID if present.
|
||||
if (tbsCertificate.Peek(CSC | 1)) {
|
||||
rv = der::ExpectTagAndSkipValue(tbsCertificate, CSC | 1);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore subjectUniqueID if present.
|
||||
if (tbsCertificate.Peek(CSC | 2)) {
|
||||
rv = der::ExpectTagAndSkipValue(tbsCertificate, CSC | 2);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
rv = der::OptionalExtensions(
|
||||
tbsCertificate, CSC | 3,
|
||||
[this](Reader& extnID, const Input& extnValue, bool critical,
|
||||
/*out*/ bool& understood) {
|
||||
return RememberExtension(extnID, extnValue, critical, understood);
|
||||
});
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// The Netscape Certificate Type extension is an obsolete
|
||||
// Netscape-proprietary mechanism that we ignore in favor of the standard
|
||||
// extensions. However, some CAs have issued certificates with the Netscape
|
||||
// Cert Type extension marked critical. Thus, for compatibility reasons, we
|
||||
// "understand" this extension by ignoring it when it is not critical, and
|
||||
// by ensuring that the equivalent standardized extensions are present when
|
||||
// it is marked critical, based on the assumption that the information in
|
||||
// the Netscape Cert Type extension is consistent with the information in
|
||||
// the standard extensions.
|
||||
//
|
||||
// Here is a mapping between the Netscape Cert Type extension and the
|
||||
// standard extensions:
|
||||
//
|
||||
// Netscape Cert Type | BasicConstraints.cA | Extended Key Usage
|
||||
// --------------------+-----------------------+----------------------
|
||||
// SSL Server | false | id_kp_serverAuth
|
||||
// SSL Client | false | id_kp_clientAuth
|
||||
// S/MIME Client | false | id_kp_emailProtection
|
||||
// Object Signing | false | id_kp_codeSigning
|
||||
// SSL Server CA | true | id_kp_serverAuth
|
||||
// SSL Client CA | true | id_kp_clientAuth
|
||||
// S/MIME CA | true | id_kp_emailProtection
|
||||
// Object Signing CA | true | id_kp_codeSigning
|
||||
if (criticalNetscapeCertificateType.GetLength() > 0 &&
|
||||
(basicConstraints.GetLength() == 0 || extKeyUsage.GetLength() == 0)) {
|
||||
return Result::ERROR_UNKNOWN_CRITICAL_EXTENSION;
|
||||
}
|
||||
|
||||
return der::End(tbsCertificate);
|
||||
}
|
||||
|
||||
Result
|
||||
BackCert::RememberExtension(Reader& extnID, Input extnValue,
|
||||
bool critical, /*out*/ bool& understood)
|
||||
{
|
||||
understood = false;
|
||||
|
||||
// python DottedOIDToCode.py id-ce-keyUsage 2.5.29.15
|
||||
static const uint8_t id_ce_keyUsage[] = {
|
||||
0x55, 0x1d, 0x0f
|
||||
};
|
||||
// python DottedOIDToCode.py id-ce-subjectAltName 2.5.29.17
|
||||
static const uint8_t id_ce_subjectAltName[] = {
|
||||
0x55, 0x1d, 0x11
|
||||
};
|
||||
// python DottedOIDToCode.py id-ce-basicConstraints 2.5.29.19
|
||||
static const uint8_t id_ce_basicConstraints[] = {
|
||||
0x55, 0x1d, 0x13
|
||||
};
|
||||
// python DottedOIDToCode.py id-ce-nameConstraints 2.5.29.30
|
||||
static const uint8_t id_ce_nameConstraints[] = {
|
||||
0x55, 0x1d, 0x1e
|
||||
};
|
||||
// python DottedOIDToCode.py id-ce-certificatePolicies 2.5.29.32
|
||||
static const uint8_t id_ce_certificatePolicies[] = {
|
||||
0x55, 0x1d, 0x20
|
||||
};
|
||||
// python DottedOIDToCode.py id-ce-policyConstraints 2.5.29.36
|
||||
static const uint8_t id_ce_policyConstraints[] = {
|
||||
0x55, 0x1d, 0x24
|
||||
};
|
||||
// python DottedOIDToCode.py id-ce-extKeyUsage 2.5.29.37
|
||||
static const uint8_t id_ce_extKeyUsage[] = {
|
||||
0x55, 0x1d, 0x25
|
||||
};
|
||||
// python DottedOIDToCode.py id-ce-inhibitAnyPolicy 2.5.29.54
|
||||
static const uint8_t id_ce_inhibitAnyPolicy[] = {
|
||||
0x55, 0x1d, 0x36
|
||||
};
|
||||
// python DottedOIDToCode.py id-pe-authorityInfoAccess 1.3.6.1.5.5.7.1.1
|
||||
static const uint8_t id_pe_authorityInfoAccess[] = {
|
||||
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
|
||||
};
|
||||
// python DottedOIDToCode.py id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5
|
||||
static const uint8_t id_pkix_ocsp_nocheck[] = {
|
||||
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05
|
||||
};
|
||||
// python DottedOIDToCode.py Netscape-certificate-type 2.16.840.1.113730.1.1
|
||||
static const uint8_t Netscape_certificate_type[] = {
|
||||
0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01
|
||||
};
|
||||
// python DottedOIDToCode.py id-pe-tlsfeature 1.3.6.1.5.5.7.1.24
|
||||
static const uint8_t id_pe_tlsfeature[] = {
|
||||
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18
|
||||
};
|
||||
// python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
|
||||
// See Section 3.3 of RFC 6962.
|
||||
static const uint8_t id_embeddedSctList[] = {
|
||||
0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
|
||||
};
|
||||
|
||||
Input* out = nullptr;
|
||||
|
||||
// We already enforce the maximum possible constraints for policies so we
|
||||
// can safely ignore even critical policy constraint extensions.
|
||||
//
|
||||
// XXX: Doing it this way won't allow us to detect duplicate
|
||||
// policyConstraints extensions, but that's OK because (and only because) we
|
||||
// ignore the extension.
|
||||
Input dummyPolicyConstraints;
|
||||
|
||||
// We don't need to save the contents of this extension if it is present. We
|
||||
// just need to handle its presence (it is essentially ignored right now).
|
||||
Input dummyOCSPNocheck;
|
||||
|
||||
// For compatibility reasons, for some extensions we have to allow empty
|
||||
// extension values. This would normally interfere with our duplicate
|
||||
// extension checking code. However, as long as the extensions we allow to
|
||||
// have empty values are also the ones we implicitly allow duplicates of,
|
||||
// this will work fine.
|
||||
bool emptyValueAllowed = false;
|
||||
|
||||
// RFC says "Conforming CAs MUST mark this extension as non-critical" for
|
||||
// both authorityKeyIdentifier and subjectKeyIdentifier, and we do not use
|
||||
// them for anything, so we totally ignore them here.
|
||||
|
||||
if (extnID.MatchRest(id_ce_keyUsage)) {
|
||||
out = &keyUsage;
|
||||
} else if (extnID.MatchRest(id_ce_subjectAltName)) {
|
||||
out = &subjectAltName;
|
||||
} else if (extnID.MatchRest(id_ce_basicConstraints)) {
|
||||
out = &basicConstraints;
|
||||
} else if (extnID.MatchRest(id_ce_nameConstraints)) {
|
||||
out = &nameConstraints;
|
||||
} else if (extnID.MatchRest(id_ce_certificatePolicies)) {
|
||||
out = &certificatePolicies;
|
||||
} else if (extnID.MatchRest(id_ce_policyConstraints)) {
|
||||
out = &dummyPolicyConstraints;
|
||||
} else if (extnID.MatchRest(id_ce_extKeyUsage)) {
|
||||
out = &extKeyUsage;
|
||||
} else if (extnID.MatchRest(id_ce_inhibitAnyPolicy)) {
|
||||
out = &inhibitAnyPolicy;
|
||||
} else if (extnID.MatchRest(id_pe_authorityInfoAccess)) {
|
||||
out = &authorityInfoAccess;
|
||||
} else if (extnID.MatchRest(id_pe_tlsfeature)) {
|
||||
out = &requiredTLSFeatures;
|
||||
} else if (extnID.MatchRest(id_embeddedSctList)) {
|
||||
out = &signedCertificateTimestamps;
|
||||
} else if (extnID.MatchRest(id_pkix_ocsp_nocheck) && critical) {
|
||||
// We need to make sure we don't reject delegated OCSP response signing
|
||||
// certificates that contain the id-pkix-ocsp-nocheck extension marked as
|
||||
// critical when validating OCSP responses. Without this, an application
|
||||
// that implements soft-fail OCSP might ignore a valid Revoked or Unknown
|
||||
// response, and an application that implements hard-fail OCSP might fail
|
||||
// to connect to a server given a valid Good response.
|
||||
out = &dummyOCSPNocheck;
|
||||
// We allow this extension to have an empty value.
|
||||
// See http://comments.gmane.org/gmane.ietf.x509/30947
|
||||
emptyValueAllowed = true;
|
||||
} else if (extnID.MatchRest(Netscape_certificate_type) && critical) {
|
||||
out = &criticalNetscapeCertificateType;
|
||||
}
|
||||
|
||||
if (out) {
|
||||
// Don't allow an empty value for any extension we understand. This way, we
|
||||
// can test out->GetLength() != 0 or out->Init() to check for duplicates.
|
||||
if (extnValue.GetLength() == 0 && !emptyValueAllowed) {
|
||||
return Result::ERROR_EXTENSION_VALUE_INVALID;
|
||||
}
|
||||
if (out->Init(extnValue) != Success) {
|
||||
// Duplicate extension
|
||||
return Result::ERROR_EXTENSION_VALUE_INVALID;
|
||||
}
|
||||
understood = true;
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
|
||||
Input& sctList)
|
||||
{
|
||||
Reader decodedValue;
|
||||
Result rv = der::ExpectTagAndGetValueAtEnd(extnValue, der::OCTET_STRING,
|
||||
decodedValue);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return decodedValue.SkipToEnd(sctList);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::pkix
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,611 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/pkixder.h"
|
||||
|
||||
#include "mozpkix/pkixutil.h"
|
||||
|
||||
namespace mozilla { namespace pkix { namespace der {
|
||||
|
||||
// Too complicated to be inline
|
||||
Result
|
||||
ReadTagAndGetValue(Reader& input, /*out*/ uint8_t& tag, /*out*/ Input& value)
|
||||
{
|
||||
Result rv;
|
||||
|
||||
rv = input.Read(tag);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if ((tag & 0x1F) == 0x1F) {
|
||||
return Result::ERROR_BAD_DER; // high tag number form not allowed
|
||||
}
|
||||
|
||||
uint16_t length;
|
||||
|
||||
// The short form of length is a single byte with the high order bit set
|
||||
// to zero. The long form of length is one byte with the high order bit
|
||||
// set, followed by N bytes, where N is encoded in the lowest 7 bits of
|
||||
// the first byte.
|
||||
uint8_t length1;
|
||||
rv = input.Read(length1);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (!(length1 & 0x80)) {
|
||||
length = length1;
|
||||
} else if (length1 == 0x81) {
|
||||
uint8_t length2;
|
||||
rv = input.Read(length2);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (length2 < 128) {
|
||||
// Not shortest possible encoding
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
length = length2;
|
||||
} else if (length1 == 0x82) {
|
||||
rv = input.Read(length);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (length < 256) {
|
||||
// Not shortest possible encoding
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
} else {
|
||||
// We don't support lengths larger than 2^16 - 1.
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
|
||||
return input.Skip(length, value);
|
||||
}
|
||||
|
||||
static Result
|
||||
OptionalNull(Reader& input)
|
||||
{
|
||||
if (input.Peek(NULLTag)) {
|
||||
return Null(input);
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Result
|
||||
AlgorithmIdentifierValue(Reader& input, /*out*/ Reader& algorithmOIDValue)
|
||||
{
|
||||
Result rv = ExpectTagAndGetValue(input, der::OIDTag, algorithmOIDValue);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return OptionalNull(input);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result
|
||||
SignatureAlgorithmIdentifierValue(Reader& input,
|
||||
/*out*/ PublicKeyAlgorithm& publicKeyAlgorithm,
|
||||
/*out*/ DigestAlgorithm& digestAlgorithm)
|
||||
{
|
||||
// RFC 5758 Section 3.2 (ECDSA with SHA-2), and RFC 3279 Section 2.2.3
|
||||
// (ECDSA with SHA-1) say that parameters must be omitted.
|
||||
//
|
||||
// RFC 4055 Section 5 and RFC 3279 Section 2.2.1 both say that parameters for
|
||||
// RSA must be encoded as NULL; we relax that requirement by allowing the
|
||||
// NULL to be omitted, to match all the other signature algorithms we support
|
||||
// and for compatibility.
|
||||
Reader algorithmID;
|
||||
Result rv = AlgorithmIdentifierValue(input, algorithmID);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// RFC 5758 Section 3.2 (ecdsa-with-SHA224 is intentionally excluded)
|
||||
// python DottedOIDToCode.py ecdsa-with-SHA256 1.2.840.10045.4.3.2
|
||||
static const uint8_t ecdsa_with_SHA256[] = {
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02
|
||||
};
|
||||
// python DottedOIDToCode.py ecdsa-with-SHA384 1.2.840.10045.4.3.3
|
||||
static const uint8_t ecdsa_with_SHA384[] = {
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03
|
||||
};
|
||||
// python DottedOIDToCode.py ecdsa-with-SHA512 1.2.840.10045.4.3.4
|
||||
static const uint8_t ecdsa_with_SHA512[] = {
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04
|
||||
};
|
||||
|
||||
// RFC 4055 Section 5 (sha224WithRSAEncryption is intentionally excluded)
|
||||
// python DottedOIDToCode.py sha256WithRSAEncryption 1.2.840.113549.1.1.11
|
||||
static const uint8_t sha256WithRSAEncryption[] = {
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b
|
||||
};
|
||||
// python DottedOIDToCode.py sha384WithRSAEncryption 1.2.840.113549.1.1.12
|
||||
static const uint8_t sha384WithRSAEncryption[] = {
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c
|
||||
};
|
||||
// python DottedOIDToCode.py sha512WithRSAEncryption 1.2.840.113549.1.1.13
|
||||
static const uint8_t sha512WithRSAEncryption[] = {
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d
|
||||
};
|
||||
|
||||
// RFC 3279 Section 2.2.1
|
||||
// python DottedOIDToCode.py sha-1WithRSAEncryption 1.2.840.113549.1.1.5
|
||||
static const uint8_t sha_1WithRSAEncryption[] = {
|
||||
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05
|
||||
};
|
||||
|
||||
// NIST Open Systems Environment (OSE) Implementor's Workshop (OIW)
|
||||
// http://www.oiw.org/agreements/stable/12s-9412.txt (no longer works).
|
||||
// http://www.imc.org/ietf-pkix/old-archive-97/msg01166.html
|
||||
// We need to support this this non-PKIX OID for compatibility.
|
||||
// python DottedOIDToCode.py sha1WithRSASignature 1.3.14.3.2.29
|
||||
static const uint8_t sha1WithRSASignature[] = {
|
||||
0x2b, 0x0e, 0x03, 0x02, 0x1d
|
||||
};
|
||||
|
||||
// RFC 3279 Section 2.2.3
|
||||
// python DottedOIDToCode.py ecdsa-with-SHA1 1.2.840.10045.4.1
|
||||
static const uint8_t ecdsa_with_SHA1[] = {
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01
|
||||
};
|
||||
|
||||
// Matching is attempted based on a rough estimate of the commonality of the
|
||||
// algorithm, to minimize the number of MatchRest calls.
|
||||
if (algorithmID.MatchRest(sha256WithRSAEncryption)) {
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
|
||||
digestAlgorithm = DigestAlgorithm::sha256;
|
||||
} else if (algorithmID.MatchRest(ecdsa_with_SHA256)) {
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
|
||||
digestAlgorithm = DigestAlgorithm::sha256;
|
||||
} else if (algorithmID.MatchRest(sha_1WithRSAEncryption)) {
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
|
||||
digestAlgorithm = DigestAlgorithm::sha1;
|
||||
} else if (algorithmID.MatchRest(ecdsa_with_SHA1)) {
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
|
||||
digestAlgorithm = DigestAlgorithm::sha1;
|
||||
} else if (algorithmID.MatchRest(ecdsa_with_SHA384)) {
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
|
||||
digestAlgorithm = DigestAlgorithm::sha384;
|
||||
} else if (algorithmID.MatchRest(ecdsa_with_SHA512)) {
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
|
||||
digestAlgorithm = DigestAlgorithm::sha512;
|
||||
} else if (algorithmID.MatchRest(sha384WithRSAEncryption)) {
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
|
||||
digestAlgorithm = DigestAlgorithm::sha384;
|
||||
} else if (algorithmID.MatchRest(sha512WithRSAEncryption)) {
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
|
||||
digestAlgorithm = DigestAlgorithm::sha512;
|
||||
} else if (algorithmID.MatchRest(sha1WithRSASignature)) {
|
||||
// XXX(bug 1042479): recognize this old OID for compatibility.
|
||||
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
|
||||
digestAlgorithm = DigestAlgorithm::sha1;
|
||||
} else {
|
||||
return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
DigestAlgorithmIdentifier(Reader& input, /*out*/ DigestAlgorithm& algorithm)
|
||||
{
|
||||
return der::Nested(input, SEQUENCE, [&algorithm](Reader& r) -> Result {
|
||||
Reader algorithmID;
|
||||
Result rv = AlgorithmIdentifierValue(r, algorithmID);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// RFC 4055 Section 2.1
|
||||
// python DottedOIDToCode.py id-sha1 1.3.14.3.2.26
|
||||
static const uint8_t id_sha1[] = {
|
||||
0x2b, 0x0e, 0x03, 0x02, 0x1a
|
||||
};
|
||||
// python DottedOIDToCode.py id-sha256 2.16.840.1.101.3.4.2.1
|
||||
static const uint8_t id_sha256[] = {
|
||||
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
|
||||
};
|
||||
// python DottedOIDToCode.py id-sha384 2.16.840.1.101.3.4.2.2
|
||||
static const uint8_t id_sha384[] = {
|
||||
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02
|
||||
};
|
||||
// python DottedOIDToCode.py id-sha512 2.16.840.1.101.3.4.2.3
|
||||
static const uint8_t id_sha512[] = {
|
||||
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03
|
||||
};
|
||||
|
||||
// Matching is attempted based on a rough estimate of the commonality of the
|
||||
// algorithm, to minimize the number of MatchRest calls.
|
||||
if (algorithmID.MatchRest(id_sha1)) {
|
||||
algorithm = DigestAlgorithm::sha1;
|
||||
} else if (algorithmID.MatchRest(id_sha256)) {
|
||||
algorithm = DigestAlgorithm::sha256;
|
||||
} else if (algorithmID.MatchRest(id_sha384)) {
|
||||
algorithm = DigestAlgorithm::sha384;
|
||||
} else if (algorithmID.MatchRest(id_sha512)) {
|
||||
algorithm = DigestAlgorithm::sha512;
|
||||
} else {
|
||||
return Result::ERROR_INVALID_ALGORITHM;
|
||||
}
|
||||
|
||||
return Success;
|
||||
});
|
||||
}
|
||||
|
||||
Result
|
||||
SignedData(Reader& input, /*out*/ Reader& tbs,
|
||||
/*out*/ SignedDataWithSignature& signedData)
|
||||
{
|
||||
Reader::Mark mark(input.GetMark());
|
||||
|
||||
Result rv;
|
||||
rv = ExpectTagAndGetValue(input, SEQUENCE, tbs);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = input.GetInput(mark, signedData.data);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = ExpectTagAndGetValue(input, der::SEQUENCE, signedData.algorithm);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = BitStringWithNoUnusedBits(input, signedData.signature);
|
||||
if (rv == Result::ERROR_BAD_DER) {
|
||||
rv = Result::ERROR_BAD_SIGNATURE;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Result
|
||||
BitStringWithNoUnusedBits(Reader& input, /*out*/ Input& value)
|
||||
{
|
||||
Reader valueWithUnusedBits;
|
||||
Result rv = ExpectTagAndGetValue(input, BIT_STRING, valueWithUnusedBits);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
uint8_t unusedBitsAtEnd;
|
||||
if (valueWithUnusedBits.Read(unusedBitsAtEnd) != Success) {
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
// XXX: Really the constraint should be that unusedBitsAtEnd must be less
|
||||
// than 7. But, we suspect there are no real-world values in OCSP responses
|
||||
// or certificates with non-zero unused bits. It seems like NSS assumes this
|
||||
// in various places, so we enforce it too in order to simplify this code. If
|
||||
// we find compatibility issues, we'll know we're wrong and we'll have to
|
||||
// figure out how to shift the bits around.
|
||||
if (unusedBitsAtEnd != 0) {
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
return valueWithUnusedBits.SkipToEnd(value);
|
||||
}
|
||||
|
||||
static inline Result
|
||||
ReadDigit(Reader& input, /*out*/ unsigned int& value)
|
||||
{
|
||||
uint8_t b;
|
||||
if (input.Read(b) != Success) {
|
||||
return Result::ERROR_INVALID_DER_TIME;
|
||||
}
|
||||
if (b < '0' || b > '9') {
|
||||
return Result::ERROR_INVALID_DER_TIME;
|
||||
}
|
||||
value = static_cast<unsigned int>(b - static_cast<uint8_t>('0'));
|
||||
return Success;
|
||||
}
|
||||
|
||||
static inline Result
|
||||
ReadTwoDigits(Reader& input, unsigned int minValue, unsigned int maxValue,
|
||||
/*out*/ unsigned int& value)
|
||||
{
|
||||
unsigned int hi;
|
||||
Result rv = ReadDigit(input, hi);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
unsigned int lo;
|
||||
rv = ReadDigit(input, lo);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
value = (hi * 10) + lo;
|
||||
if (value < minValue || value > maxValue) {
|
||||
return Result::ERROR_INVALID_DER_TIME;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
// We parse GeneralizedTime and UTCTime according to RFC 5280 and we do not
|
||||
// accept all time formats allowed in the ASN.1 spec. That is,
|
||||
// GeneralizedTime must always be in the format YYYYMMDDHHMMSSZ and UTCTime
|
||||
// must always be in the format YYMMDDHHMMSSZ. Timezone formats of the form
|
||||
// +HH:MM or -HH:MM or NOT accepted.
|
||||
Result
|
||||
TimeChoice(Reader& tagged, uint8_t expectedTag, /*out*/ Time& time)
|
||||
{
|
||||
unsigned int days;
|
||||
|
||||
Reader input;
|
||||
Result rv = ExpectTagAndGetValue(tagged, expectedTag, input);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
unsigned int yearHi;
|
||||
unsigned int yearLo;
|
||||
if (expectedTag == GENERALIZED_TIME) {
|
||||
rv = ReadTwoDigits(input, 0, 99, yearHi);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = ReadTwoDigits(input, 0, 99, yearLo);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
} else if (expectedTag == UTCTime) {
|
||||
rv = ReadTwoDigits(input, 0, 99, yearLo);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
yearHi = yearLo >= 50u ? 19u : 20u;
|
||||
} else {
|
||||
return NotReached("invalid tag given to TimeChoice",
|
||||
Result::ERROR_INVALID_DER_TIME);
|
||||
}
|
||||
unsigned int year = (yearHi * 100u) + yearLo;
|
||||
if (year < 1970u) {
|
||||
// We don't support dates before January 1, 1970 because that is the epoch.
|
||||
return Result::ERROR_INVALID_DER_TIME;
|
||||
}
|
||||
days = DaysBeforeYear(year);
|
||||
|
||||
unsigned int month;
|
||||
rv = ReadTwoDigits(input, 1u, 12u, month);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
unsigned int daysInMonth;
|
||||
static const unsigned int jan = 31u;
|
||||
const unsigned int feb = ((year % 4u == 0u) &&
|
||||
((year % 100u != 0u) || (year % 400u == 0u)))
|
||||
? 29u
|
||||
: 28u;
|
||||
static const unsigned int mar = 31u;
|
||||
static const unsigned int apr = 30u;
|
||||
static const unsigned int may = 31u;
|
||||
static const unsigned int jun = 30u;
|
||||
static const unsigned int jul = 31u;
|
||||
static const unsigned int aug = 31u;
|
||||
static const unsigned int sep = 30u;
|
||||
static const unsigned int oct = 31u;
|
||||
static const unsigned int nov = 30u;
|
||||
static const unsigned int dec = 31u;
|
||||
switch (month) {
|
||||
case 1: daysInMonth = jan; break;
|
||||
case 2: daysInMonth = feb; days += jan; break;
|
||||
case 3: daysInMonth = mar; days += jan + feb; break;
|
||||
case 4: daysInMonth = apr; days += jan + feb + mar; break;
|
||||
case 5: daysInMonth = may; days += jan + feb + mar + apr; break;
|
||||
case 6: daysInMonth = jun; days += jan + feb + mar + apr + may; break;
|
||||
case 7: daysInMonth = jul; days += jan + feb + mar + apr + may + jun;
|
||||
break;
|
||||
case 8: daysInMonth = aug; days += jan + feb + mar + apr + may + jun +
|
||||
jul;
|
||||
break;
|
||||
case 9: daysInMonth = sep; days += jan + feb + mar + apr + may + jun +
|
||||
jul + aug;
|
||||
break;
|
||||
case 10: daysInMonth = oct; days += jan + feb + mar + apr + may + jun +
|
||||
jul + aug + sep;
|
||||
break;
|
||||
case 11: daysInMonth = nov; days += jan + feb + mar + apr + may + jun +
|
||||
jul + aug + sep + oct;
|
||||
break;
|
||||
case 12: daysInMonth = dec; days += jan + feb + mar + apr + may + jun +
|
||||
jul + aug + sep + oct + nov;
|
||||
break;
|
||||
default:
|
||||
return NotReached("month already bounds-checked by ReadTwoDigits",
|
||||
Result::FATAL_ERROR_INVALID_STATE);
|
||||
}
|
||||
|
||||
unsigned int dayOfMonth;
|
||||
rv = ReadTwoDigits(input, 1u, daysInMonth, dayOfMonth);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
days += dayOfMonth - 1;
|
||||
|
||||
unsigned int hours;
|
||||
rv = ReadTwoDigits(input, 0u, 23u, hours);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
unsigned int minutes;
|
||||
rv = ReadTwoDigits(input, 0u, 59u, minutes);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
unsigned int seconds;
|
||||
rv = ReadTwoDigits(input, 0u, 59u, seconds);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
uint8_t b;
|
||||
if (input.Read(b) != Success) {
|
||||
return Result::ERROR_INVALID_DER_TIME;
|
||||
}
|
||||
if (b != 'Z') {
|
||||
return Result::ERROR_INVALID_DER_TIME;
|
||||
}
|
||||
if (End(input) != Success) {
|
||||
return Result::ERROR_INVALID_DER_TIME;
|
||||
}
|
||||
|
||||
uint64_t totalSeconds = (static_cast<uint64_t>(days) * 24u * 60u * 60u) +
|
||||
(static_cast<uint64_t>(hours) * 60u * 60u) +
|
||||
(static_cast<uint64_t>(minutes) * 60u) +
|
||||
seconds;
|
||||
|
||||
time = TimeFromElapsedSecondsAD(totalSeconds);
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
IntegralBytes(Reader& input, uint8_t tag,
|
||||
IntegralValueRestriction valueRestriction,
|
||||
/*out*/ Input& value,
|
||||
/*optional out*/ Input::size_type* significantBytes)
|
||||
{
|
||||
Result rv = ExpectTagAndGetValue(input, tag, value);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Reader reader(value);
|
||||
|
||||
// There must be at least one byte in the value. (Zero is encoded with a
|
||||
// single 0x00 value byte.)
|
||||
uint8_t firstByte;
|
||||
rv = reader.Read(firstByte);
|
||||
if (rv != Success) {
|
||||
if (rv == Result::ERROR_BAD_DER) {
|
||||
return Result::ERROR_INVALID_INTEGER_ENCODING;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
// If there is a byte after an initial 0x00/0xFF, then the initial byte
|
||||
// indicates a positive/negative integer value with its high bit set/unset.
|
||||
bool prefixed = !reader.AtEnd() && (firstByte == 0 || firstByte == 0xff);
|
||||
|
||||
if (prefixed) {
|
||||
uint8_t nextByte;
|
||||
if (reader.Read(nextByte) != Success) {
|
||||
return NotReached("Read of one byte failed but not at end.",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
if ((firstByte & 0x80) == (nextByte & 0x80)) {
|
||||
return Result::ERROR_INVALID_INTEGER_ENCODING;
|
||||
}
|
||||
}
|
||||
|
||||
switch (valueRestriction) {
|
||||
case IntegralValueRestriction::MustBe0To127:
|
||||
if (value.GetLength() != 1 || (firstByte & 0x80) != 0) {
|
||||
return Result::ERROR_INVALID_INTEGER_ENCODING;
|
||||
}
|
||||
break;
|
||||
|
||||
case IntegralValueRestriction::MustBePositive:
|
||||
if ((value.GetLength() == 1 && firstByte == 0) ||
|
||||
(firstByte & 0x80) != 0) {
|
||||
return Result::ERROR_INVALID_INTEGER_ENCODING;
|
||||
}
|
||||
break;
|
||||
|
||||
case IntegralValueRestriction::NoRestriction:
|
||||
break;
|
||||
}
|
||||
|
||||
if (significantBytes) {
|
||||
*significantBytes = value.GetLength();
|
||||
if (prefixed) {
|
||||
assert(*significantBytes > 1);
|
||||
--*significantBytes;
|
||||
}
|
||||
|
||||
assert(*significantBytes > 0);
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
// This parser will only parse values between 0..127. If this range is
|
||||
// increased then callers will need to be changed.
|
||||
Result
|
||||
IntegralValue(Reader& input, uint8_t tag, /*out*/ uint8_t& value)
|
||||
{
|
||||
// Conveniently, all the Integers that we actually have to be able to parse
|
||||
// are positive and very small. Consequently, this parser is *much* simpler
|
||||
// than a general Integer parser would need to be.
|
||||
Input valueBytes;
|
||||
Result rv = IntegralBytes(input, tag, IntegralValueRestriction::MustBe0To127,
|
||||
valueBytes, nullptr);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Reader valueReader(valueBytes);
|
||||
rv = valueReader.Read(value);
|
||||
if (rv != Success) {
|
||||
return NotReached("IntegralBytes already validated the value.", rv);
|
||||
}
|
||||
rv = End(valueReader);
|
||||
assert(rv == Success); // guaranteed by IntegralBytes's range checks.
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
Result
|
||||
OptionalVersion(Reader& input, /*out*/ Version& version)
|
||||
{
|
||||
static const uint8_t TAG = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
|
||||
if (!input.Peek(TAG)) {
|
||||
version = Version::v1;
|
||||
return Success;
|
||||
}
|
||||
return Nested(input, TAG, [&version](Reader& value) -> Result {
|
||||
uint8_t integerValue;
|
||||
Result rv = Integer(value, integerValue);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
// XXX(bug 1031093): We shouldn't accept an explicit encoding of v1,
|
||||
// but we do here for compatibility reasons.
|
||||
switch (integerValue) {
|
||||
case static_cast<uint8_t>(Version::v3): version = Version::v3; break;
|
||||
case static_cast<uint8_t>(Version::v2): version = Version::v2; break;
|
||||
case static_cast<uint8_t>(Version::v1): version = Version::v1; break;
|
||||
case static_cast<uint8_t>(Version::v4): version = Version::v4; break;
|
||||
default:
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
return Success;
|
||||
});
|
||||
}
|
||||
|
||||
} } } // namespace mozilla::pkix::der
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,236 @@
|
|||
/*- *- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/pkixnss.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "cryptohi.h"
|
||||
#include "keyhi.h"
|
||||
#include "pk11pub.h"
|
||||
#include "mozpkix/nss_scoped_ptrs.h"
|
||||
#include "mozpkix/pkix.h"
|
||||
#include "mozpkix/pkixutil.h"
|
||||
#include "secerr.h"
|
||||
#include "sslerr.h"
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
namespace {
|
||||
|
||||
Result
|
||||
VerifySignedDigest(const SignedDigest& sd,
|
||||
Input subjectPublicKeyInfo,
|
||||
SECOidTag pubKeyAlg,
|
||||
void* pkcs11PinArg)
|
||||
{
|
||||
SECOidTag digestAlg;
|
||||
switch (sd.digestAlgorithm) {
|
||||
case DigestAlgorithm::sha512: digestAlg = SEC_OID_SHA512; break;
|
||||
case DigestAlgorithm::sha384: digestAlg = SEC_OID_SHA384; break;
|
||||
case DigestAlgorithm::sha256: digestAlg = SEC_OID_SHA256; break;
|
||||
case DigestAlgorithm::sha1: digestAlg = SEC_OID_SHA1; break;
|
||||
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
}
|
||||
|
||||
SECItem subjectPublicKeyInfoSECItem =
|
||||
UnsafeMapInputToSECItem(subjectPublicKeyInfo);
|
||||
ScopedCERTSubjectPublicKeyInfo
|
||||
spki(SECKEY_DecodeDERSubjectPublicKeyInfo(&subjectPublicKeyInfoSECItem));
|
||||
if (!spki) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
ScopedSECKEYPublicKey
|
||||
pubKey(SECKEY_ExtractPublicKey(spki.get()));
|
||||
if (!pubKey) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
|
||||
SECItem digestSECItem(UnsafeMapInputToSECItem(sd.digest));
|
||||
SECItem signatureSECItem(UnsafeMapInputToSECItem(sd.signature));
|
||||
SECStatus srv = VFY_VerifyDigestDirect(&digestSECItem, pubKey.get(),
|
||||
&signatureSECItem, pubKeyAlg,
|
||||
digestAlg, pkcs11PinArg);
|
||||
if (srv != SECSuccess) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result
|
||||
VerifyRSAPKCS1SignedDigestNSS(const SignedDigest& sd,
|
||||
Input subjectPublicKeyInfo,
|
||||
void* pkcs11PinArg)
|
||||
{
|
||||
return VerifySignedDigest(sd, subjectPublicKeyInfo,
|
||||
SEC_OID_PKCS1_RSA_ENCRYPTION, pkcs11PinArg);
|
||||
}
|
||||
|
||||
Result
|
||||
VerifyECDSASignedDigestNSS(const SignedDigest& sd,
|
||||
Input subjectPublicKeyInfo,
|
||||
void* pkcs11PinArg)
|
||||
{
|
||||
return VerifySignedDigest(sd, subjectPublicKeyInfo,
|
||||
SEC_OID_ANSIX962_EC_PUBLIC_KEY, pkcs11PinArg);
|
||||
}
|
||||
|
||||
Result
|
||||
DigestBufNSS(Input item,
|
||||
DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf,
|
||||
size_t digestBufLen)
|
||||
{
|
||||
SECOidTag oid;
|
||||
size_t bits;
|
||||
switch (digestAlg) {
|
||||
case DigestAlgorithm::sha512: oid = SEC_OID_SHA512; bits = 512; break;
|
||||
case DigestAlgorithm::sha384: oid = SEC_OID_SHA384; bits = 384; break;
|
||||
case DigestAlgorithm::sha256: oid = SEC_OID_SHA256; bits = 256; break;
|
||||
case DigestAlgorithm::sha1: oid = SEC_OID_SHA1; bits = 160; break;
|
||||
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
}
|
||||
if (digestBufLen != bits / 8) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
SECItem itemSECItem = UnsafeMapInputToSECItem(item);
|
||||
if (itemSECItem.len >
|
||||
static_cast<decltype(itemSECItem.len)>(
|
||||
std::numeric_limits<int32_t>::max())) {
|
||||
PR_NOT_REACHED("large items should not be possible here");
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
SECStatus srv = PK11_HashBuf(oid, digestBuf, itemSECItem.data,
|
||||
static_cast<int32_t>(itemSECItem.len));
|
||||
if (srv != SECSuccess) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
MapPRErrorCodeToResult(PRErrorCode error)
|
||||
{
|
||||
switch (error)
|
||||
{
|
||||
#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
|
||||
case nss_result: return Result::mozilla_pkix_result;
|
||||
|
||||
MOZILLA_PKIX_MAP_LIST
|
||||
|
||||
#undef MOZILLA_PKIX_MAP
|
||||
|
||||
default:
|
||||
return Result::ERROR_UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
PRErrorCode
|
||||
MapResultToPRErrorCode(Result result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
|
||||
case Result::mozilla_pkix_result: return nss_result;
|
||||
|
||||
MOZILLA_PKIX_MAP_LIST
|
||||
|
||||
#undef MOZILLA_PKIX_MAP
|
||||
|
||||
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RegisterErrorTable()
|
||||
{
|
||||
// Note that these error strings are not localizable.
|
||||
// When these strings change, update the localization information too.
|
||||
static const PRErrorMessage ErrorTableText[] = {
|
||||
{ "MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE",
|
||||
"The server uses key pinning (HPKP) but no trusted certificate chain "
|
||||
"could be constructed that matches the pinset. Key pinning violations "
|
||||
"cannot be overridden." },
|
||||
{ "MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY",
|
||||
"The server uses a certificate with a basic constraints extension "
|
||||
"identifying it as a certificate authority. For a properly-issued "
|
||||
"certificate, this should not be the case." },
|
||||
{ "MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE",
|
||||
"The server presented a certificate with a key size that is too small "
|
||||
"to establish a secure connection." },
|
||||
{ "MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA",
|
||||
"An X.509 version 1 certificate that is not a trust anchor was used to "
|
||||
"issue the server's certificate. X.509 version 1 certificates are "
|
||||
"deprecated and should not be used to sign other certificates." },
|
||||
{ "MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH",
|
||||
"The certificate is not valid for the given email address." },
|
||||
{ "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE",
|
||||
"The server presented a certificate that is not yet valid." },
|
||||
{ "MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE",
|
||||
"A certificate that is not yet valid was used to issue the server's "
|
||||
"certificate." },
|
||||
{ "MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH",
|
||||
"The signature algorithm in the signature field of the certificate does "
|
||||
"not match the algorithm in its signatureAlgorithm field." },
|
||||
{ "MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING",
|
||||
"The OCSP response does not include a status for the certificate being "
|
||||
"verified." },
|
||||
{ "MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG",
|
||||
"The server presented a certificate that is valid for too long." },
|
||||
{ "MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING",
|
||||
"A required TLS feature is missing." },
|
||||
{ "MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING",
|
||||
"The server presented a certificate that contains an invalid encoding of "
|
||||
"an integer. Common causes include negative serial numbers, negative RSA "
|
||||
"moduli, and encodings that are longer than necessary." },
|
||||
{ "MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME",
|
||||
"The server presented a certificate with an empty issuer distinguished "
|
||||
"name." },
|
||||
{ "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED",
|
||||
"An additional policy constraint failed when validating this "
|
||||
"certificate." },
|
||||
{ "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT",
|
||||
"The certificate is not trusted because it is self-signed." },
|
||||
{ "MOZILLA_PKIX_ERROR_MITM_DETECTED",
|
||||
"Your connection is being intercepted by a TLS proxy. Uninstall it if "
|
||||
"possible or configure your device to trust its root certificate." },
|
||||
};
|
||||
// Note that these error strings are not localizable.
|
||||
// When these strings change, update the localization information too.
|
||||
|
||||
static const PRErrorTable ErrorTable = {
|
||||
ErrorTableText,
|
||||
"pkixerrors",
|
||||
ERROR_BASE,
|
||||
PR_ARRAY_SIZE(ErrorTableText)
|
||||
};
|
||||
|
||||
(void) PR_ErrorInstallTable(&ErrorTable);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::pkix
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,46 @@
|
|||
/*- *- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/Result.h"
|
||||
#include "mozpkix/pkixutil.h"
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
const char*
|
||||
MapResultToName(Result result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
|
||||
case Result::mozilla_pkix_result: return "Result::" #mozilla_pkix_result;
|
||||
|
||||
MOZILLA_PKIX_MAP_LIST
|
||||
|
||||
#undef MOZILLA_PKIX_MAP
|
||||
|
||||
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
}
|
||||
}
|
||||
|
||||
} } // namespace mozilla::pkix
|
|
@ -0,0 +1,78 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2014 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/Time.h"
|
||||
#include "mozpkix/pkixutil.h"
|
||||
|
||||
#ifdef _WINDOWS
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push, 3)
|
||||
#endif
|
||||
#include "windows.h"
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
#else
|
||||
#include "sys/time.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
Time
|
||||
Now()
|
||||
{
|
||||
uint64_t seconds;
|
||||
|
||||
#ifdef _WINDOWS
|
||||
// "Contains a 64-bit value representing the number of 100-nanosecond
|
||||
// intervals since January 1, 1601 (UTC)."
|
||||
// - http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
uint64_t ft64 = (static_cast<uint64_t>(ft.dwHighDateTime) << 32) |
|
||||
ft.dwLowDateTime;
|
||||
seconds = (DaysBeforeYear(1601) * Time::ONE_DAY_IN_SECONDS) +
|
||||
ft64 / (1000u * 1000u * 1000u / 100u);
|
||||
#else
|
||||
// "The gettimeofday() function shall obtain the current time, expressed as
|
||||
// seconds and microseconds since the Epoch."
|
||||
// - http://pubs.opengroup.org/onlinepubs/009695399/functions/gettimeofday.html
|
||||
timeval tv;
|
||||
(void) gettimeofday(&tv, nullptr);
|
||||
seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) +
|
||||
static_cast<uint64_t>(tv.tv_sec);
|
||||
#endif
|
||||
|
||||
return TimeFromElapsedSecondsAD(seconds);
|
||||
}
|
||||
|
||||
Time
|
||||
TimeFromEpochInSeconds(uint64_t secondsSinceEpoch)
|
||||
{
|
||||
uint64_t seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) +
|
||||
secondsSinceEpoch;
|
||||
return TimeFromElapsedSecondsAD(seconds);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::pkix
|
|
@ -0,0 +1,106 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2015 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/pkixutil.h"
|
||||
|
||||
namespace mozilla { namespace pkix {
|
||||
|
||||
Result
|
||||
DigestSignedData(TrustDomain& trustDomain,
|
||||
const der::SignedDataWithSignature& signedData,
|
||||
/*out*/ uint8_t(&digestBuf)[MAX_DIGEST_SIZE_IN_BYTES],
|
||||
/*out*/ der::PublicKeyAlgorithm& publicKeyAlg,
|
||||
/*out*/ SignedDigest& signedDigest)
|
||||
{
|
||||
Reader signatureAlg(signedData.algorithm);
|
||||
Result rv = der::SignatureAlgorithmIdentifierValue(
|
||||
signatureAlg, publicKeyAlg, signedDigest.digestAlgorithm);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (!signatureAlg.AtEnd()) {
|
||||
return Result::ERROR_BAD_DER;
|
||||
}
|
||||
|
||||
size_t digestLen;
|
||||
switch (signedDigest.digestAlgorithm) {
|
||||
case DigestAlgorithm::sha512: digestLen = 512 / 8; break;
|
||||
case DigestAlgorithm::sha384: digestLen = 384 / 8; break;
|
||||
case DigestAlgorithm::sha256: digestLen = 256 / 8; break;
|
||||
case DigestAlgorithm::sha1: digestLen = 160 / 8; break;
|
||||
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
}
|
||||
assert(digestLen <= sizeof(digestBuf));
|
||||
|
||||
rv = trustDomain.DigestBuf(signedData.data, signedDigest.digestAlgorithm,
|
||||
digestBuf, digestLen);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = signedDigest.digest.Init(digestBuf, digestLen);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return signedDigest.signature.Init(signedData.signature);
|
||||
}
|
||||
|
||||
Result
|
||||
VerifySignedDigest(TrustDomain& trustDomain,
|
||||
der::PublicKeyAlgorithm publicKeyAlg,
|
||||
const SignedDigest& signedDigest,
|
||||
Input signerSubjectPublicKeyInfo)
|
||||
{
|
||||
switch (publicKeyAlg) {
|
||||
case der::PublicKeyAlgorithm::ECDSA:
|
||||
return trustDomain.VerifyECDSASignedDigest(signedDigest,
|
||||
signerSubjectPublicKeyInfo);
|
||||
case der::PublicKeyAlgorithm::RSA_PKCS1:
|
||||
return trustDomain.VerifyRSAPKCS1SignedDigest(signedDigest,
|
||||
signerSubjectPublicKeyInfo);
|
||||
case der::PublicKeyAlgorithm::Uninitialized:
|
||||
assert(false);
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
}
|
||||
}
|
||||
|
||||
Result
|
||||
VerifySignedData(TrustDomain& trustDomain,
|
||||
const der::SignedDataWithSignature& signedData,
|
||||
Input signerSubjectPublicKeyInfo)
|
||||
{
|
||||
uint8_t digestBuf[MAX_DIGEST_SIZE_IN_BYTES];
|
||||
der::PublicKeyAlgorithm publicKeyAlg;
|
||||
SignedDigest signedDigest;
|
||||
Result rv = DigestSignedData(trustDomain, signedData, digestBuf,
|
||||
publicKeyAlg, signedDigest);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return VerifySignedDigest(trustDomain, publicKeyAlg, signedDigest,
|
||||
signerSubjectPublicKeyInfo);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::pkix
|
|
@ -0,0 +1,60 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
{
|
||||
'includes': [
|
||||
'../../coreconf/config.gypi'
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'mozpkix',
|
||||
'type': 'static_library',
|
||||
'standalone_static_library': 1,
|
||||
'sources': [
|
||||
'lib/pkixbuild.cpp',
|
||||
'lib/pkixcert.cpp',
|
||||
'lib/pkixcheck.cpp',
|
||||
'lib/pkixder.cpp',
|
||||
'lib/pkixnames.cpp',
|
||||
'lib/pkixnss.cpp',
|
||||
'lib/pkixocsp.cpp',
|
||||
'lib/pkixresult.cpp',
|
||||
'lib/pkixtime.cpp',
|
||||
'lib/pkixverify.cpp',
|
||||
],
|
||||
'dependencies': [
|
||||
'<(DEPTH)/exports.gyp:nss_mozpkix_exports',
|
||||
],
|
||||
'conditions': [
|
||||
[ 'mozpkix_only==0', {
|
||||
'dependencies': [
|
||||
'<(DEPTH)/exports.gyp:nss_exports'
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'mozpkix-testlib',
|
||||
'type': 'static_library',
|
||||
'standalone_static_library': 1,
|
||||
'sources': [
|
||||
'test-lib/pkixtestalg.cpp',
|
||||
'test-lib/pkixtestnss.cpp',
|
||||
'test-lib/pkixtestutil.cpp',
|
||||
],
|
||||
'dependencies': [
|
||||
'<(DEPTH)/exports.gyp:nss_mozpkix_exports',
|
||||
],
|
||||
'conditions': [
|
||||
[ 'mozpkix_only==0', {
|
||||
'dependencies': [
|
||||
'<(DEPTH)/exports.gyp:nss_exports'
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
],
|
||||
'variables': {
|
||||
'module': 'nss',
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2015 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/test/pkixtestutil.h"
|
||||
|
||||
#include "mozpkix/pkixder.h"
|
||||
#include "mozpkix/nss_scoped_ptrs.h"
|
||||
|
||||
// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10040 1.2.840.10040
|
||||
#define PREFIX_1_2_840_10040 0x2a, 0x86, 0x48, 0xce, 0x38
|
||||
|
||||
// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045
|
||||
#define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d
|
||||
|
||||
// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_113549 1.2.840.113549
|
||||
#define PREFIX_1_2_840_113549 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d
|
||||
|
||||
namespace mozilla { namespace pkix { namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
enum class NULLParam { NO, YES };
|
||||
|
||||
template <size_t SIZE>
|
||||
ByteString
|
||||
OID(const uint8_t (&rawValue)[SIZE])
|
||||
{
|
||||
return TLV(der::OIDTag, ByteString(rawValue, SIZE));
|
||||
}
|
||||
|
||||
template <size_t SIZE>
|
||||
ByteString
|
||||
SimpleAlgID(const uint8_t (&rawValue)[SIZE],
|
||||
NULLParam nullParam = NULLParam::NO)
|
||||
{
|
||||
ByteString sequenceValue(OID(rawValue));
|
||||
if (nullParam == NULLParam::YES) {
|
||||
sequenceValue.append(TLV(der::NULLTag, ByteString()));
|
||||
}
|
||||
return TLV(der::SEQUENCE, sequenceValue);
|
||||
}
|
||||
|
||||
template <size_t SIZE>
|
||||
ByteString
|
||||
DERInteger(const uint8_t (&rawValue)[SIZE])
|
||||
{
|
||||
ByteString value(rawValue, SIZE);
|
||||
if (value[0] & 0x80u) {
|
||||
// Prefix with a leading zero to disambiguate this from a negative value.
|
||||
value.insert(value.begin(), 0x00);
|
||||
}
|
||||
return TLV(der::INTEGER, value);
|
||||
}
|
||||
|
||||
// Generated with "openssl dsaparam -C -noout 2048" and reformatted.
|
||||
// openssl 1.0 or later must be used so that a 256-bit Q value is
|
||||
// generated.
|
||||
static const uint8_t DSS_P_RAW[] =
|
||||
{
|
||||
0xB3,0xCD,0x29,0x44,0xF0,0x25,0xA7,0x73,0xFC,0x86,0x70,0xA2,
|
||||
0x69,0x5A,0x97,0x3F,0xBD,0x1C,0x6F,0xAA,0x4A,0x40,0x42,0x8E,
|
||||
0xCF,0xAE,0x62,0x12,0xED,0xB4,0xFD,0x05,0xC2,0xAE,0xB1,0x8C,
|
||||
0xFC,0xBE,0x38,0x90,0xBB,0x7C,0xFF,0x16,0xF4,0xED,0xCE,0x72,
|
||||
0x12,0x93,0x83,0xF0,0xA4,0xA1,0x71,0xDC,0x4B,0xF0,0x4E,0x3A,
|
||||
0x2B,0xFA,0x17,0xB7,0xB3,0x2A,0xCC,0x2C,0xD3,0xC8,0x21,0x49,
|
||||
0x7A,0x83,0x71,0x8B,0x3D,0x62,0x96,0xDC,0xAD,0xA8,0x03,0xBE,
|
||||
0x1D,0x33,0x11,0xF3,0xEB,0xD8,0x1B,0x8D,0xDB,0x62,0x79,0x83,
|
||||
0xF8,0x67,0x4E,0x62,0x21,0x2C,0x81,0x59,0xE8,0x73,0xD7,0xAF,
|
||||
0xB9,0x63,0x60,0xEA,0xAE,0xEC,0x68,0x6A,0xB4,0xB0,0x65,0xBA,
|
||||
0xA3,0x4C,0x09,0x99,0x29,0x6A,0x2E,0x2B,0xFC,0x6D,0x51,0xCA,
|
||||
0x30,0xA2,0x2F,0x7A,0x65,0x76,0xA7,0x55,0x13,0x11,0xA0,0x02,
|
||||
0xA2,0x59,0x4B,0xCE,0xA7,0x05,0xF6,0x07,0x35,0x9B,0x41,0xD7,
|
||||
0x11,0x5A,0x18,0x57,0xA7,0x78,0x88,0xC3,0xA8,0xE3,0x39,0xF5,
|
||||
0x47,0x3D,0x2E,0x18,0x54,0xB0,0xF0,0xBF,0x65,0x3F,0x77,0xC7,
|
||||
0x11,0xB8,0x0D,0x52,0xAD,0xC8,0xE8,0x6D,0xF6,0x7E,0x88,0x65,
|
||||
0x84,0x2B,0xF7,0xEF,0x8E,0xB5,0x7C,0xBD,0x2E,0x0D,0xF3,0xC6,
|
||||
0xDD,0x0B,0xB4,0xF2,0x23,0x1F,0xDA,0x55,0x05,0xF5,0xDC,0x53,
|
||||
0xA6,0x83,0xDA,0x5C,0xEF,0x29,0x02,0x78,0x68,0xD0,0xA4,0x39,
|
||||
0x09,0x7F,0xFA,0x49,0x18,0xD0,0xB5,0x19,0x35,0x31,0x8E,0xDE,
|
||||
0x43,0x35,0xA3,0xB9,0x6D,0xC1,0x70,0xC6,0x0D,0x18,0x24,0xEB,
|
||||
0x1E,0x4D,0x52,0xB7,
|
||||
};
|
||||
|
||||
static const uint8_t DSS_Q_RAW[] =
|
||||
{
|
||||
0x8D,0x6B,0x86,0x89,0x9C,0x8D,0x30,0x91,0xCC,0x6E,0x34,0xF1,
|
||||
0xE8,0x9C,0x8A,0x5C,0xD6,0xAB,0x01,0x1E,0xC4,0xDB,0xFD,0x07,
|
||||
0xEB,0x5F,0x4E,0xE8,0xFA,0xFC,0x98,0x2D,
|
||||
};
|
||||
|
||||
static const uint8_t DSS_G_RAW[] =
|
||||
{
|
||||
0x0E,0x2C,0x34,0xB2,0xE1,0x66,0x49,0xB6,0x9A,0x7D,0x67,0x3E,
|
||||
0xEE,0x98,0x35,0x18,0x28,0x35,0xFC,0x05,0x36,0x3B,0x94,0xE6,
|
||||
0x1E,0x1C,0x5B,0x05,0x3E,0x86,0x1B,0xE3,0xED,0xD2,0xE1,0xF3,
|
||||
0xF7,0xF7,0x60,0x6D,0x7D,0xA1,0xAF,0x9A,0xD1,0xDF,0xA2,0x9C,
|
||||
0xFC,0xA2,0xEB,0x90,0x8B,0x1C,0x82,0x92,0x45,0x7B,0x30,0x2A,
|
||||
0xFD,0x7A,0xE6,0x68,0x8F,0xEC,0x89,0x3A,0x9A,0xAD,0xFE,0x25,
|
||||
0x5E,0x51,0xC5,0x29,0x45,0x7F,0xAC,0xDE,0xFC,0xB4,0x1B,0x3A,
|
||||
0xDA,0xC7,0x21,0x68,0x87,0x27,0x8D,0x7B,0xB2,0xBB,0x41,0x60,
|
||||
0x46,0x42,0x5B,0x6B,0xE8,0x80,0xD2,0xE4,0xA3,0x30,0x8F,0xD5,
|
||||
0x71,0x07,0x8A,0x7B,0x32,0x56,0x84,0x41,0x1C,0xDF,0x69,0xE9,
|
||||
0xFD,0xBA,0x48,0xE0,0x43,0xA0,0x38,0x92,0x12,0xF3,0x52,0xA5,
|
||||
0x40,0x87,0xCB,0x34,0xBB,0x3E,0x25,0x29,0x3C,0xC6,0xA5,0x17,
|
||||
0xFD,0x58,0x47,0x89,0xDB,0x9B,0xB9,0xCF,0xE9,0xA8,0xF2,0xEC,
|
||||
0x55,0x76,0xF5,0xF1,0x9C,0x6E,0x0A,0x3F,0x16,0x5F,0x49,0x31,
|
||||
0x31,0x1C,0x43,0xA2,0x83,0xDA,0xDD,0x7F,0x1C,0xEA,0x05,0x36,
|
||||
0x7B,0xED,0x09,0xFB,0x6F,0x8A,0x2B,0x55,0xB9,0xBC,0x4A,0x8C,
|
||||
0x28,0xC1,0x4D,0x13,0x6E,0x47,0xF4,0xAD,0x79,0x00,0xE9,0x5A,
|
||||
0xB6,0xC7,0x73,0x28,0xA9,0x89,0xAD,0xE8,0x6E,0xC6,0x54,0xA5,
|
||||
0x56,0x2D,0xAA,0x81,0x83,0x9E,0xC1,0x13,0x79,0xA4,0x12,0xE0,
|
||||
0x76,0x1F,0x25,0x43,0xB6,0xDE,0x56,0xF7,0x52,0xCC,0x07,0xB8,
|
||||
0x37,0xE2,0x8C,0xC5,0x56,0x8C,0xDD,0x63,0xF5,0xB6,0xA3,0x46,
|
||||
0x62,0xF6,0x35,0x76,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TestSignatureAlgorithm::TestSignatureAlgorithm(
|
||||
const TestPublicKeyAlgorithm& aPublicKeyAlg,
|
||||
TestDigestAlgorithmID aDigestAlg,
|
||||
const ByteString& aAlgorithmIdentifier,
|
||||
bool aAccepted)
|
||||
: publicKeyAlg(aPublicKeyAlg)
|
||||
, digestAlg(aDigestAlg)
|
||||
, algorithmIdentifier(aAlgorithmIdentifier)
|
||||
, accepted(aAccepted)
|
||||
{
|
||||
}
|
||||
|
||||
ByteString DSS_P() { return ByteString(DSS_P_RAW, sizeof(DSS_P_RAW)); }
|
||||
ByteString DSS_Q() { return ByteString(DSS_Q_RAW, sizeof(DSS_Q_RAW)); }
|
||||
ByteString DSS_G() { return ByteString(DSS_G_RAW, sizeof(DSS_G_RAW)); }
|
||||
|
||||
TestPublicKeyAlgorithm
|
||||
DSS()
|
||||
{
|
||||
static const uint8_t oidValue[] = { PREFIX_1_2_840_10040, 4, 1 };
|
||||
|
||||
// RFC 3279 Section-2.3.2
|
||||
return TestPublicKeyAlgorithm(
|
||||
TLV(der::SEQUENCE,
|
||||
OID(oidValue) +
|
||||
TLV(der::SEQUENCE,
|
||||
DERInteger(DSS_P_RAW) +
|
||||
DERInteger(DSS_Q_RAW) +
|
||||
DERInteger(DSS_G_RAW))));
|
||||
}
|
||||
|
||||
// RFC 3279 Section 2.3.1
|
||||
TestPublicKeyAlgorithm
|
||||
RSA_PKCS1()
|
||||
{
|
||||
static const uint8_t rsaEncryption[] = { PREFIX_1_2_840_113549, 1, 1, 1 };
|
||||
return TestPublicKeyAlgorithm(SimpleAlgID(rsaEncryption, NULLParam::YES));
|
||||
}
|
||||
|
||||
// RFC 3279 Section 2.2.1
|
||||
TestSignatureAlgorithm md2WithRSAEncryption()
|
||||
{
|
||||
static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 2 };
|
||||
return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD2,
|
||||
SimpleAlgID(oidValue), false);
|
||||
}
|
||||
|
||||
// RFC 3279 Section 2.2.1
|
||||
TestSignatureAlgorithm md5WithRSAEncryption()
|
||||
{
|
||||
static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 4 };
|
||||
return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD5,
|
||||
SimpleAlgID(oidValue), false);
|
||||
}
|
||||
|
||||
// RFC 3279 Section 2.2.1
|
||||
TestSignatureAlgorithm sha1WithRSAEncryption()
|
||||
{
|
||||
static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 5 };
|
||||
return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA1,
|
||||
SimpleAlgID(oidValue), true);
|
||||
}
|
||||
|
||||
// RFC 4055 Section 5
|
||||
TestSignatureAlgorithm sha256WithRSAEncryption()
|
||||
{
|
||||
static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 11 };
|
||||
return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA256,
|
||||
SimpleAlgID(oidValue), true);
|
||||
}
|
||||
|
||||
} } } // namespace mozilla::pkix
|
|
@ -0,0 +1,364 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This code is made available to you under your choice of the following sets
|
||||
* of licensing terms:
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/* Copyright 2013 Mozilla Contributors
|
||||
*
|
||||
* 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 "mozpkix/test/pkixtestutil.h"
|
||||
#include "mozpkix/test/pkixtestnss.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "cryptohi.h"
|
||||
#include "keyhi.h"
|
||||
#include "nss.h"
|
||||
#include "pk11pqg.h"
|
||||
#include "pk11pub.h"
|
||||
#include "mozpkix/nss_scoped_ptrs.h"
|
||||
#include "mozpkix/pkixnss.h"
|
||||
#include "mozpkix/pkixder.h"
|
||||
#include "mozpkix/pkixutil.h"
|
||||
#include "prinit.h"
|
||||
#include "secerr.h"
|
||||
#include "secitem.h"
|
||||
|
||||
namespace mozilla { namespace pkix { namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
TestKeyPair* GenerateKeyPairInner();
|
||||
|
||||
void
|
||||
InitNSSIfNeeded()
|
||||
{
|
||||
if (NSS_NoDB_Init(nullptr) != SECSuccess) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static ScopedTestKeyPair reusedKeyPair;
|
||||
|
||||
PRStatus
|
||||
InitReusedKeyPair()
|
||||
{
|
||||
InitNSSIfNeeded();
|
||||
reusedKeyPair.reset(GenerateKeyPairInner());
|
||||
return reusedKeyPair ? PR_SUCCESS : PR_FAILURE;
|
||||
}
|
||||
|
||||
class NSSTestKeyPair final : public TestKeyPair
|
||||
{
|
||||
public:
|
||||
NSSTestKeyPair(const TestPublicKeyAlgorithm& aPublicKeyAlg,
|
||||
const ByteString& spk,
|
||||
const ByteString& aEncryptedPrivateKey,
|
||||
const ByteString& aEncryptionAlgorithm,
|
||||
const ByteString& aEncryptionParams)
|
||||
: TestKeyPair(aPublicKeyAlg, spk)
|
||||
, encryptedPrivateKey(aEncryptedPrivateKey)
|
||||
, encryptionAlgorithm(aEncryptionAlgorithm)
|
||||
, encryptionParams(aEncryptionParams)
|
||||
{
|
||||
}
|
||||
|
||||
Result SignData(const ByteString& tbs,
|
||||
const TestSignatureAlgorithm& signatureAlgorithm,
|
||||
/*out*/ ByteString& signature) const override
|
||||
{
|
||||
SECOidTag oidTag;
|
||||
if (signatureAlgorithm.publicKeyAlg == RSA_PKCS1()) {
|
||||
switch (signatureAlgorithm.digestAlg) {
|
||||
case TestDigestAlgorithmID::MD2:
|
||||
oidTag = SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION;
|
||||
break;
|
||||
case TestDigestAlgorithmID::MD5:
|
||||
oidTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION;
|
||||
break;
|
||||
case TestDigestAlgorithmID::SHA1:
|
||||
oidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION;
|
||||
break;
|
||||
case TestDigestAlgorithmID::SHA224:
|
||||
oidTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION;
|
||||
break;
|
||||
case TestDigestAlgorithmID::SHA256:
|
||||
oidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION;
|
||||
break;
|
||||
case TestDigestAlgorithmID::SHA384:
|
||||
oidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION;
|
||||
break;
|
||||
case TestDigestAlgorithmID::SHA512:
|
||||
oidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION;
|
||||
break;
|
||||
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
|
||||
}
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
|
||||
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
if (!slot) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
SECItem encryptedPrivateKeyInfoItem = {
|
||||
siBuffer,
|
||||
const_cast<uint8_t*>(encryptedPrivateKey.data()),
|
||||
static_cast<unsigned int>(encryptedPrivateKey.length())
|
||||
};
|
||||
SECItem encryptionAlgorithmItem = {
|
||||
siBuffer,
|
||||
const_cast<uint8_t*>(encryptionAlgorithm.data()),
|
||||
static_cast<unsigned int>(encryptionAlgorithm.length())
|
||||
};
|
||||
SECItem encryptionParamsItem = {
|
||||
siBuffer,
|
||||
const_cast<uint8_t*>(encryptionParams.data()),
|
||||
static_cast<unsigned int>(encryptionParams.length())
|
||||
};
|
||||
SECKEYEncryptedPrivateKeyInfo encryptedPrivateKeyInfo = {
|
||||
nullptr,
|
||||
{ encryptionAlgorithmItem, encryptionParamsItem },
|
||||
encryptedPrivateKeyInfoItem
|
||||
};
|
||||
SECItem passwordItem = { siBuffer, nullptr, 0 };
|
||||
SECItem publicValueItem = {
|
||||
siBuffer,
|
||||
const_cast<uint8_t*>(subjectPublicKey.data()),
|
||||
static_cast<unsigned int>(subjectPublicKey.length())
|
||||
};
|
||||
SECKEYPrivateKey* privateKey;
|
||||
// This should always be an RSA key (we'll have aborted above if we're not
|
||||
// doing an RSA signature).
|
||||
if (PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(
|
||||
slot.get(), &encryptedPrivateKeyInfo, &passwordItem, nullptr,
|
||||
&publicValueItem, false, false, rsaKey, KU_ALL, &privateKey,
|
||||
nullptr) != SECSuccess) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
ScopedSECKEYPrivateKey scopedPrivateKey(privateKey);
|
||||
SECItem signatureItem;
|
||||
if (SEC_SignData(&signatureItem, tbs.data(),
|
||||
static_cast<int>(tbs.length()),
|
||||
scopedPrivateKey.get(), oidTag) != SECSuccess) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
signature.assign(signatureItem.data, signatureItem.len);
|
||||
SECITEM_FreeItem(&signatureItem, false);
|
||||
return Success;
|
||||
}
|
||||
|
||||
TestKeyPair* Clone() const override
|
||||
{
|
||||
return new (std::nothrow) NSSTestKeyPair(publicKeyAlg,
|
||||
subjectPublicKey,
|
||||
encryptedPrivateKey,
|
||||
encryptionAlgorithm,
|
||||
encryptionParams);
|
||||
}
|
||||
|
||||
private:
|
||||
const ByteString encryptedPrivateKey;
|
||||
const ByteString encryptionAlgorithm;
|
||||
const ByteString encryptionParams;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// This private function is also used by Gecko's PSM test framework
|
||||
// (OCSPCommon.cpp).
|
||||
TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg,
|
||||
const ScopedSECKEYPublicKey& publicKey,
|
||||
const ScopedSECKEYPrivateKey& privateKey)
|
||||
{
|
||||
ScopedCERTSubjectPublicKeyInfo
|
||||
spki(SECKEY_CreateSubjectPublicKeyInfo(publicKey.get()));
|
||||
if (!spki) {
|
||||
return nullptr;
|
||||
}
|
||||
SECItem spkDER = spki->subjectPublicKey;
|
||||
DER_ConvertBitString(&spkDER); // bits to bytes
|
||||
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
if (!slot) {
|
||||
return nullptr;
|
||||
}
|
||||
// Because NSSTestKeyPair isn't tracked by XPCOM and won't otherwise be aware
|
||||
// of shutdown, we don't have a way to release NSS resources at the
|
||||
// appropriate time. To work around this, NSSTestKeyPair doesn't hold on to
|
||||
// NSS resources. Instead, we export the generated private key part as an
|
||||
// encrypted blob (with an empty password and fairly lame encryption). When we
|
||||
// need to use it (e.g. to sign something), we decrypt it and create a
|
||||
// temporary key object.
|
||||
SECItem passwordItem = { siBuffer, nullptr, 0 };
|
||||
ScopedSECKEYEncryptedPrivateKeyInfo encryptedPrivateKey(
|
||||
PK11_ExportEncryptedPrivKeyInfo(
|
||||
slot.get(), SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC,
|
||||
&passwordItem, privateKey.get(), 1, nullptr));
|
||||
if (!encryptedPrivateKey) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new (std::nothrow) NSSTestKeyPair(
|
||||
publicKeyAlg,
|
||||
ByteString(spkDER.data, spkDER.len),
|
||||
ByteString(encryptedPrivateKey->encryptedData.data,
|
||||
encryptedPrivateKey->encryptedData.len),
|
||||
ByteString(encryptedPrivateKey->algorithm.algorithm.data,
|
||||
encryptedPrivateKey->algorithm.algorithm.len),
|
||||
ByteString(encryptedPrivateKey->algorithm.parameters.data,
|
||||
encryptedPrivateKey->algorithm.parameters.len));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
TestKeyPair*
|
||||
GenerateKeyPairInner()
|
||||
{
|
||||
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
if (!slot) {
|
||||
abort();
|
||||
}
|
||||
|
||||
// Bug 1012786: PK11_GenerateKeyPair can fail if there is insufficient
|
||||
// entropy to generate a random key. Attempting to add some entropy and
|
||||
// retrying appears to solve this issue.
|
||||
for (uint32_t retries = 0; retries < 10; retries++) {
|
||||
PK11RSAGenParams params;
|
||||
params.keySizeInBits = 2048;
|
||||
params.pe = 3;
|
||||
SECKEYPublicKey* publicKeyTemp = nullptr;
|
||||
ScopedSECKEYPrivateKey
|
||||
privateKey(PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN,
|
||||
¶ms, &publicKeyTemp, false, true,
|
||||
nullptr));
|
||||
ScopedSECKEYPublicKey publicKey(publicKeyTemp);
|
||||
if (privateKey) {
|
||||
return CreateTestKeyPair(RSA_PKCS1(), publicKey, privateKey);
|
||||
}
|
||||
|
||||
assert(!publicKeyTemp);
|
||||
|
||||
if (PR_GetError() != SEC_ERROR_PKCS11_FUNCTION_FAILED) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Since these keys are only for testing, we don't need them to be good,
|
||||
// random keys.
|
||||
// https://xkcd.com/221/
|
||||
static const uint8_t RANDOM_NUMBER[] = { 4, 4, 4, 4, 4, 4, 4, 4 };
|
||||
if (PK11_RandomUpdate((void*) &RANDOM_NUMBER,
|
||||
sizeof(RANDOM_NUMBER)) != SECSuccess) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TestKeyPair*
|
||||
GenerateKeyPair()
|
||||
{
|
||||
InitNSSIfNeeded();
|
||||
return GenerateKeyPairInner();
|
||||
}
|
||||
|
||||
TestKeyPair*
|
||||
CloneReusedKeyPair()
|
||||
{
|
||||
static PRCallOnceType initCallOnce;
|
||||
if (PR_CallOnce(&initCallOnce, InitReusedKeyPair) != PR_SUCCESS) {
|
||||
abort();
|
||||
}
|
||||
assert(reusedKeyPair);
|
||||
return reusedKeyPair->Clone();
|
||||
}
|
||||
|
||||
TestKeyPair*
|
||||
GenerateDSSKeyPair()
|
||||
{
|
||||
InitNSSIfNeeded();
|
||||
|
||||
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
if (!slot) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ByteString p(DSS_P());
|
||||
ByteString q(DSS_Q());
|
||||
ByteString g(DSS_G());
|
||||
|
||||
static const PQGParams PARAMS = {
|
||||
nullptr,
|
||||
{ siBuffer,
|
||||
const_cast<uint8_t*>(p.data()),
|
||||
static_cast<unsigned int>(p.length())
|
||||
},
|
||||
{ siBuffer,
|
||||
const_cast<uint8_t*>(q.data()),
|
||||
static_cast<unsigned int>(q.length())
|
||||
},
|
||||
{ siBuffer,
|
||||
const_cast<uint8_t*>(g.data()),
|
||||
static_cast<unsigned int>(g.length())
|
||||
}
|
||||
};
|
||||
|
||||
SECKEYPublicKey* publicKeyTemp = nullptr;
|
||||
ScopedSECKEYPrivateKey
|
||||
privateKey(PK11_GenerateKeyPair(slot.get(), CKM_DSA_KEY_PAIR_GEN,
|
||||
const_cast<PQGParams*>(&PARAMS),
|
||||
&publicKeyTemp, false, true, nullptr));
|
||||
if (!privateKey) {
|
||||
return nullptr;
|
||||
}
|
||||
ScopedSECKEYPublicKey publicKey(publicKeyTemp);
|
||||
return CreateTestKeyPair(DSS(), publicKey, privateKey);
|
||||
}
|
||||
|
||||
Result
|
||||
TestVerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo)
|
||||
{
|
||||
InitNSSIfNeeded();
|
||||
return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
Result
|
||||
TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo)
|
||||
{
|
||||
InitNSSIfNeeded();
|
||||
return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
Result
|
||||
TestDigestBuf(Input item,
|
||||
DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf,
|
||||
size_t digestBufLen)
|
||||
{
|
||||
InitNSSIfNeeded();
|
||||
return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
|
||||
}
|
||||
|
||||
} } } // namespace mozilla::pkix::test
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,216 @@
|
|||
# This code is made available to you under your choice of the following sets
|
||||
# of licensing terms:
|
||||
###############################################################################
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
###############################################################################
|
||||
# Copyright 2013 Mozilla Contributors
|
||||
#
|
||||
# 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.
|
||||
|
||||
from __future__ import print_function
|
||||
import argparse
|
||||
import itertools
|
||||
import sys
|
||||
|
||||
|
||||
def base128(value):
|
||||
"""
|
||||
Given an integral value, returns an array of the base-128 representation
|
||||
of that value, with all but the last byte having the high bit set as
|
||||
required by the DER rules for the nodes of an OID after the first two
|
||||
bytes.
|
||||
|
||||
>>> base128(1)
|
||||
[1]
|
||||
>>> base128(10045)
|
||||
[206, 61]
|
||||
"""
|
||||
|
||||
if value < 0:
|
||||
raise ValueError("An OID must have only positive-value nodes.")
|
||||
|
||||
# least significant byte has highest bit unset
|
||||
result = [value % 0x80]
|
||||
value /= 0x80
|
||||
|
||||
while value != 0:
|
||||
result = [0x80 | (value % 0x80)] + result
|
||||
value /= 0x80
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def dottedOIDToEncodedArray(dottedOID):
|
||||
"""
|
||||
Takes a dotted OID string (e.g. '1.2.840.10045.4.3.4') as input, and
|
||||
returns an array that contains the DER encoding of its value, without
|
||||
the tag and length (e.g. [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04]).
|
||||
"""
|
||||
nodes = [int(x) for x in dottedOID.strip().split(".")]
|
||||
if len(nodes) < 2:
|
||||
raise ValueError("An OID must have at least two nodes.")
|
||||
if not (0 <= nodes[0] <= 2):
|
||||
raise ValueError("The first node of an OID must be 0, 1, or 2.")
|
||||
if not (0 <= nodes[1] <= 39):
|
||||
# XXX: Does this restriction apply when the first part is 2?
|
||||
raise ValueError("The second node of an OID must be 0-39.")
|
||||
firstByte = (40 * nodes[0]) + nodes[1]
|
||||
restBase128 = [base128(x) for x in nodes[2:]]
|
||||
return [firstByte] + list(itertools.chain.from_iterable(restBase128))
|
||||
|
||||
|
||||
def dottedOIDToCArray(dottedOID, mode):
|
||||
"""
|
||||
Takes a dotted OID string (e.g. '1.2.840.10045.4.3.4') as input, and
|
||||
returns a string that contains the hex encoding of the OID in C++ literal
|
||||
notation, e.g. '0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04'.
|
||||
"""
|
||||
bytes = dottedOIDToEncodedArray(dottedOID)
|
||||
|
||||
if mode != "value" and mode != "prefixdefine":
|
||||
bytes = [0x06, len(bytes)] + bytes
|
||||
|
||||
if mode == "alg":
|
||||
# Wrap the DER-encoded OID in a SEQUENCE to create an
|
||||
# AlgorithmIdentifier with no parameters.
|
||||
bytes = [0x30, len(bytes)] + bytes
|
||||
|
||||
return ", ".join(["0x%.2x" % b for b in bytes])
|
||||
|
||||
|
||||
def specNameToCName(specName):
|
||||
"""
|
||||
Given an string containing an ASN.1 name, returns a string that is a valid
|
||||
C++ identifier that is as similar to that name as possible. Since most
|
||||
ASN.1 identifiers used in PKIX specifications are legal C++ names except
|
||||
for containing hyphens, this function just converts the hyphens to
|
||||
underscores. This may need to be improved in the future if we encounter
|
||||
names with other funny characters.
|
||||
"""
|
||||
return specName.replace("-", "_")
|
||||
|
||||
|
||||
def toCode(programName, specName, dottedOID, mode):
|
||||
"""
|
||||
Given an ASN.1 name and a string containing the dotted representation of an
|
||||
OID, returns a string that contains a C++ declaration for a named constant
|
||||
that contains that OID value. If mode is "value" then only the value of
|
||||
the OID (without the tag or length) will be included in the output. If mode
|
||||
is "tlv" then the value will be prefixed with the tag and length. If mode
|
||||
is "alg" then the value will be a complete der-encoded AlgorithmIdentifier
|
||||
with no parameters.
|
||||
|
||||
This:
|
||||
|
||||
toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4",
|
||||
"value")
|
||||
|
||||
would result in a string like:
|
||||
|
||||
// python DottedOIDToCode.py ecdsa-with-SHA512 1.2.840.10045.4.3.4
|
||||
static const uint8_t ecdsa_with_SHA512[] = {
|
||||
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04
|
||||
};
|
||||
|
||||
This:
|
||||
|
||||
toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4",
|
||||
"tlv")
|
||||
|
||||
would result in a string like:
|
||||
|
||||
// python DottedOIDToCode.py --tlv ecdsa-with-SHA512 1.2.840.10045.4.3.4
|
||||
static const uint8_t tlv_ecdsa_with_SHA512[] = {
|
||||
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04
|
||||
};
|
||||
|
||||
This:
|
||||
|
||||
toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4",
|
||||
"alg")
|
||||
|
||||
would result in a string like:
|
||||
|
||||
// python DottedOIDToCode.py --alg ecdsa-with-SHA512 1.2.840.10045.4.3.4
|
||||
static const uint8_t alg_ecdsa_with_SHA512[] = {
|
||||
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04
|
||||
};
|
||||
|
||||
This:
|
||||
|
||||
toCode("DottedOIDToCode.py", "PREFIX_1_2_840_10045", "1.2.840.10045",
|
||||
"prefixdefine")
|
||||
|
||||
would result in a string like this (note the lack of indention):
|
||||
|
||||
// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045
|
||||
#define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d
|
||||
"""
|
||||
programNameWithOptions = programName
|
||||
|
||||
if mode == "prefixdefine":
|
||||
programNameWithOptions += " --prefixdefine"
|
||||
varName = specName
|
||||
return ("// python %s %s %s\n" +
|
||||
"#define %s %s\n") % (programNameWithOptions, specName,
|
||||
dottedOID, varName,
|
||||
dottedOIDToCArray(dottedOID, mode))
|
||||
|
||||
varName = specNameToCName(specName)
|
||||
if mode == "tlv":
|
||||
programNameWithOptions += " --tlv"
|
||||
varName = "tlv_" + varName
|
||||
elif mode == "alg":
|
||||
programNameWithOptions += " --alg"
|
||||
varName = "alg_" + varName
|
||||
elif mode == "prefixdefine":
|
||||
programNameWithOptions += " --alg"
|
||||
varName = varName
|
||||
|
||||
return (" // python %s %s %s\n" +
|
||||
" static const uint8_t %s[] = {\n" +
|
||||
" %s\n" +
|
||||
" };\n") % (programNameWithOptions, specName, dottedOID, varName,
|
||||
dottedOIDToCArray(dottedOID, mode))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate code snippets to handle OIDs in C++",
|
||||
epilog="example: python %s ecdsa-with-SHA1 1.2.840.10045.4.1"
|
||||
% sys.argv[0])
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--tlv", action='store_true',
|
||||
help="Wrap the encoded OID value with the tag and length")
|
||||
group.add_argument("--alg", action='store_true',
|
||||
help="Wrap the encoded OID value in an encoded SignatureAlgorithm")
|
||||
group.add_argument("--prefixdefine", action='store_true',
|
||||
help="generate a OID prefix #define")
|
||||
parser.add_argument("name",
|
||||
help="The name given to the OID in the specification")
|
||||
parser.add_argument("dottedOID", metavar="dotted-oid",
|
||||
help="The OID value, in dotted notation")
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.alg:
|
||||
mode = 'alg'
|
||||
elif args.tlv:
|
||||
mode = 'tlv'
|
||||
elif args.prefixdefine:
|
||||
mode = 'prefixdefine'
|
||||
else:
|
||||
mode = 'value'
|
||||
|
||||
print(toCode(sys.argv[0], args.name, args.dottedOID, mode))
|
|
@ -6,7 +6,7 @@
|
|||
#define util_h__
|
||||
|
||||
#include "nspr.h"
|
||||
#include "scoped_ptrs.h"
|
||||
#include "nss_scoped_ptrs.h"
|
||||
|
||||
#include <secmodt.h>
|
||||
#include <string>
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче