Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for client side OCSP stapling in s2n-tls #245

Merged
merged 16 commits into from
Sep 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
47 changes: 47 additions & 0 deletions FUZZING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,50 @@ If both sets of tests pass, refresh the fuzzer corpora with `refresh_ssl_corpora
cd fuzz
./refresh_ssl_corpora.sh /path/to/fuzzer/mode/build /path/to/non/fuzzer/mode/build
```

## Adding New Fuzz Test Targets
When adding new functionality, adding new fuzz tests are important to provide additional testing and verification that we are correct.

### Steps
1. `NEW_FUNCTION='name_of_new_fuzzing_target'`
2. `touch fuzz/${NEW_FUNCTION}.cc` \
Write fuzzing code for libfuzzer to parse in `fuzz/${NEW_FUNCTION}.cc`. The code in this file will be how Libfuzzer
identifies how to parse any data inputs the fuzzer gives. Fuzz tests test upon random data inputs, so in most cases
we write code to test on data parsing (and reserializing the data again if applicable).
3. `mkdir fuzz/${NEW_FUNCTION}_corpus_raw` \
Import any existing unit test files into the `fuzz/${NEW_FUNCTION}_corpus_raw` directory. These will be used to
create our new fuzzing corpus.
4. Add `fuzzer('name_of_new_fuzzing_target')` target in the `fuzz/CmakeLists.txt` file.
5. Build AWS-LC with fuzzing enabled (only buildable with clang)
```
mkdir test_build_dir
cmake ../aws-lc -GNinja "-B`pwd`/test_build_dir" "-DCMAKE_INSTALL_PREFIX=`pwd`/test_build_dir/" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DFUZZ=1 -DASAN=1 -DBUILD_TESTING=OFF
ninja -C test_build_dir
```
6. Run the fuzzer with our import test files by running:\
**Note**:
* `max_len`: set for maximum test file size if needed
* `max_total_time`: can be adjusted to how long you'd like the fuzzer to run.
* `workers`: should be set accordingly to how many cpu processors are available.
```
mkdir fuzz/${NEW_FUNCTION}_corpus_temp
NUM_CPU_THREADS=$(grep -c ^processor /proc/cpuinfo)
./test_build_dir/fuzz/${NEW_FUNCTION} -jobs=${NUM_CPU_THREADS} -workers=${NUM_CPU_THREADS} -timeout=5 \
-print_final_stats=1 -max_total_time=1800 fuzz/${NEW_FUNCTION}_corpus_temp fuzz/${NEW_FUNCTION}_corpus_raw
```
7. By default, the fuzzing process will continue until `max_total_time` has been reached. When a bug is found, any
crashes or sanitizer failures will be reported in the console and the particular corpus file that triggered the bug
will be written into the root directory (as `crash-<sha1>`, `leak-<sha1>`, or `timeout-<sha1>`).
If any of these files appear, we should look into what is causing the failure.
9. If no failures emerge, we can merge the generated corpus to minimize the amount of files, while still providing full coverage.
```
mkdir fuzz/${NEW_FUNCTION}_corpus
./test_build_dir/fuzz/${NEW_FUNCTION} -merge=1 fuzz/${NEW_FUNCTION}_corpus fuzz/${NEW_FUNCTION}_corpus_temp
```
10. Remove original directories, files, and fuzz logs used to generate the orginal corpus.
```
rmdir fuzz/${NEW_FUNCTION}_corpus_temp
rmdir fuzz/${NEW_FUNCTION}_corpus_raw
rm -r fuzz-*.log
```
6 changes: 6 additions & 0 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ if(GO_EXECUTABLE)
err/evp.errordata
err/hkdf.errordata
err/obj.errordata
err/ocsp.errordata
err/pem.errordata
err/pkcs7.errordata
err/pkcs8.errordata
Expand Down Expand Up @@ -340,6 +341,10 @@ add_library(
mem.c
obj/obj.c
obj/obj_xref.c
ocsp/ocsp_asn.c
ocsp/ocsp_client.c
ocsp/ocsp_lib.c
ocsp/ocsp_verify.c
pem/pem_all.c
pem/pem_info.c
pem/pem_lib.c
Expand Down Expand Up @@ -579,6 +584,7 @@ if(BUILD_TESTING)
impl_dispatch_test.cc
lhash/lhash_test.cc
obj/obj_test.cc
ocsp/ocsp_test.cc
pem/pem_test.cc
pkcs7/pkcs7_test.cc
pkcs8/pkcs8_test.cc
Expand Down
13 changes: 13 additions & 0 deletions crypto/err/ocsp.errordata
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
OCSP,101,CERTIFICATE_VERIFY_ERROR
OCSP,102,DIGEST_ERR
OCSP,103,MISSING_OCSPSIGNING_USAGE
OCSP,104,NOT_BASIC_RESPONSE
OCSP,105,NO_CERTIFICATES_IN_CHAIN
OCSP,108,NO_RESPONSE_DATA
OCSP,111,RESPONSE_CONTAINS_NO_REVOCATION_DATA
OCSP,112,ROOT_CA_NOT_TRUSTED
OCSP,117,SIGNATURE_FAILURE
OCSP,118,SIGNER_CERTIFICATE_NOT_FOUND
OCSP,119,UNKNOWN_MESSAGE_DIGEST
OCSP,120,UNKNOWN_NID
OCSP,130,NO_SIGNER_KEY
197 changes: 197 additions & 0 deletions crypto/ocsp/internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright 2015-2021 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/

#include "openssl/ocsp.h"
#include "openssl/x509.h"


// CertID ::= SEQUENCE {
// hashAlgorithm AlgorithmIdentifier,
// issuerNameHash OCTET STRING, --Hash of Issuer's DN
// issuerKeyHash OCTET STRING, --Hash of Issuers public key (excluding the tag & length fields)
// serialNumber CertificateSerialNumber }
//
struct ocsp_cert_id_st {
X509_ALGOR *hashAlgorithm;
ASN1_OCTET_STRING *issuerNameHash;
ASN1_OCTET_STRING *issuerKeyHash;
ASN1_INTEGER *serialNumber;
};

// Signature ::= SEQUENCE {
// signatureAlgorithm AlgorithmIdentifier,
// signature BIT STRING,
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
//
struct ocsp_signature_st {
X509_ALGOR *signatureAlgorithm;
ASN1_BIT_STRING *signature;
STACK_OF(X509) *certs;
};


// OCSPResponseStatus ::= ENUMERATED {
// successful (0), --Response has valid confirmations
// malformedRequest (1), --Illegal confirmation request
// internalError (2), --Internal error in issuer
// tryLater (3), --Try again later
// --(4) is not used
// sigRequired (5), --Must sign the request
// unauthorized (6) --Request unauthorized
// }
//

// ResponseBytes ::= SEQUENCE {
// responseType OBJECT IDENTIFIER,
// response OCTET STRING }
//
struct ocsp_resp_bytes_st {
ASN1_OBJECT *responseType;
ASN1_OCTET_STRING *response;
};

// OCSPResponse ::= SEQUENCE {
// responseStatus OCSPResponseStatus,
// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
//
struct ocsp_response_st {
ASN1_ENUMERATED *responseStatus;
OCSP_RESPBYTES *responseBytes;
};

// ResponderID ::= CHOICE {
// byName [1] Name,
// byKey [2] KeyHash }
//
// KeyHash ::= OCTET STRING --SHA-1 hash of responder's public key
// --(excluding the tag and length fields)
//
struct ocsp_responder_id_st {
int type;
union {
X509_NAME *byName;
ASN1_OCTET_STRING *byKey;
} value;
};



// RevokedInfo ::= SEQUENCE {
// revocationTime GeneralizedTime,
// revocationReason [0] EXPLICIT CRLReason OPTIONAL }
//
struct ocsp_revoked_info_st {
ASN1_GENERALIZEDTIME *revocationTime;
ASN1_ENUMERATED *revocationReason;
};

// CertStatus ::= CHOICE {
// good [0] IMPLICIT NULL,
// revoked [1] IMPLICIT RevokedInfo,
// unknown [2] IMPLICIT UnknownInfo }
//
struct ocsp_cert_status_st {
int type;
union {
ASN1_NULL *good;
OCSP_REVOKEDINFO *revoked;
ASN1_NULL *unknown;
} value;
};

// SingleResponse ::= SEQUENCE {
// certID CertID,
// certStatus CertStatus,
// thisUpdate GeneralizedTime,
// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
// singleExtensions [1] EXPLICIT Extensions OPTIONAL }
//
struct ocsp_single_response_st {
OCSP_CERTID *certId;
OCSP_CERTSTATUS *certStatus;
ASN1_GENERALIZEDTIME *thisUpdate;
ASN1_GENERALIZEDTIME *nextUpdate;
STACK_OF(X509_EXTENSION) *singleExtensions;
};

// ResponseData ::= SEQUENCE {
// version [0] EXPLICIT Version DEFAULT v1,
// responderID ResponderID,
// producedAt GeneralizedTime,
// responses SEQUENCE OF SingleResponse,
// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
//
struct ocsp_response_data_st {
ASN1_INTEGER *version;
OCSP_RESPID *responderId;
ASN1_GENERALIZEDTIME *producedAt;
STACK_OF(OCSP_SINGLERESP) *responses;
STACK_OF(X509_EXTENSION) *responseExtensions;
};

// BasicOCSPResponse ::= SEQUENCE {
// tbsResponseData ResponseData,
// signatureAlgorithm AlgorithmIdentifier,
// signature BIT STRING,
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
//
//
// Note 1: The value for "signature" is specified in the OCSP rfc2560 as
// follows: "The value for the signature SHALL be computed on the hash of
// the DER encoding ResponseData." This means that you must hash the
// DER-encoded tbsResponseData, and then run it through a crypto-signing
// function, which will (at least w/RSA) do a hash-'n'-private-encrypt
// operation. This seems a bit odd, but that's the spec. Also note that
// the data structures do not leave anywhere to independently specify the
// algorithm used for the initial hash. So, we look at the
// signature-specification algorithm, and try to do something intelligent.
// -- Kathy Weinhold, CertCo
//
// Note 2: It seems that the mentioned passage from RFC 2560 (section
// 4.2.1) is open for interpretation. I've done tests against another
// responder, and found that it doesn't do the double hashing that the RFC
// seems to say one should. Therefore, all relevant functions take a flag
// saying which variant should be used. -- Richard Levitte, OpenSSL team
// and CeloCom
struct ocsp_basic_response_st {
OCSP_RESPDATA *tbsResponseData;
X509_ALGOR *signatureAlgorithm;
ASN1_BIT_STRING *signature;
STACK_OF(X509) *certs;
};

// Returns |OCSP_SINGLERESP| in the index of |OCSP_BASICRESP|.
OCSP_SINGLERESP *OCSP_resp_get0(OCSP_BASICRESP *bs, size_t idx);

// Returns index of |OCSP_SINGLERESP| in |OCSP_BASICRESP| matching a
// given certificate ID, returns -1 if not found.
int OCSP_resp_find(OCSP_BASICRESP *bs, OCSP_CERTID *id, int last);

// Returns status of |OCSP_SINGLERESP|
//
// Note: 1. Reason value is allowed to be null.
// 2. Time values passed into function are allowed to be NULL if certificate
// fields are empty.
// 3. revtime and reason values only set if the certificate status is revoked.
int OCSP_single_get0_status(OCSP_SINGLERESP *single, int *reason,
ASN1_GENERALIZEDTIME **revtime,
ASN1_GENERALIZEDTIME **thisupd,
ASN1_GENERALIZEDTIME **nextupd);

OCSP_CERTID *OCSP_cert_id_new(const EVP_MD *dgst,
const X509_NAME *issuerName,
const ASN1_BIT_STRING *issuerKey,
const ASN1_INTEGER *serialNumber);

// --- OCSP compare functions ---
// Compares certificate id issuers, returns 0 on equal.
int OCSP_id_issuer_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b);

// Compares certificate id, returns 0 on equal.
int OCSP_id_cmp(const OCSP_CERTID *a, const OCSP_CERTID *b);
88 changes: 88 additions & 0 deletions crypto/ocsp/ocsp_asn.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2015-2021 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/

// OCSP ASN1 structure definitions can be found in RFC link below
// https://tools.ietf.org/html/rfc6960#section-4.2.1

#include "internal.h"


ASN1_SEQUENCE(OCSP_CERTID) = {
ASN1_SIMPLE(OCSP_CERTID, hashAlgorithm, X509_ALGOR),
ASN1_SIMPLE(OCSP_CERTID, issuerNameHash, ASN1_OCTET_STRING),
ASN1_SIMPLE(OCSP_CERTID, issuerKeyHash, ASN1_OCTET_STRING),
ASN1_SIMPLE(OCSP_CERTID, serialNumber, ASN1_INTEGER)
} ASN1_SEQUENCE_END(OCSP_CERTID)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_CERTID)

ASN1_SEQUENCE(OCSP_RESPBYTES) = {
ASN1_SIMPLE(OCSP_RESPBYTES, responseType, ASN1_OBJECT),
ASN1_SIMPLE(OCSP_RESPBYTES, response, ASN1_OCTET_STRING)
} ASN1_SEQUENCE_END(OCSP_RESPBYTES)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_RESPBYTES)

ASN1_SEQUENCE(OCSP_RESPONSE) = {
ASN1_SIMPLE(OCSP_RESPONSE, responseStatus, ASN1_ENUMERATED),
ASN1_EXP_OPT(OCSP_RESPONSE, responseBytes, OCSP_RESPBYTES, 0)
} ASN1_SEQUENCE_END(OCSP_RESPONSE)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_RESPONSE)

ASN1_CHOICE(OCSP_RESPID) = {
ASN1_EXP(OCSP_RESPID, value.byName, X509_NAME, 1),
ASN1_EXP(OCSP_RESPID, value.byKey, ASN1_OCTET_STRING, 2)
} ASN1_CHOICE_END(OCSP_RESPID)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_RESPID)

ASN1_SEQUENCE(OCSP_REVOKEDINFO) = {
ASN1_SIMPLE(OCSP_REVOKEDINFO, revocationTime, ASN1_GENERALIZEDTIME),
ASN1_EXP_OPT(OCSP_REVOKEDINFO, revocationReason, ASN1_ENUMERATED, 0)
} ASN1_SEQUENCE_END(OCSP_REVOKEDINFO)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_REVOKEDINFO)

ASN1_CHOICE(OCSP_CERTSTATUS) = {
ASN1_IMP(OCSP_CERTSTATUS, value.good, ASN1_NULL, 0),
ASN1_IMP(OCSP_CERTSTATUS, value.revoked, OCSP_REVOKEDINFO, 1),
ASN1_IMP(OCSP_CERTSTATUS, value.unknown, ASN1_NULL, 2)
} ASN1_CHOICE_END(OCSP_CERTSTATUS)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_CERTSTATUS)

ASN1_SEQUENCE(OCSP_SINGLERESP) = {
ASN1_SIMPLE(OCSP_SINGLERESP, certId, OCSP_CERTID),
ASN1_SIMPLE(OCSP_SINGLERESP, certStatus, OCSP_CERTSTATUS),
ASN1_SIMPLE(OCSP_SINGLERESP, thisUpdate, ASN1_GENERALIZEDTIME),
ASN1_EXP_OPT(OCSP_SINGLERESP, nextUpdate, ASN1_GENERALIZEDTIME, 0),
ASN1_EXP_SEQUENCE_OF_OPT(OCSP_SINGLERESP, singleExtensions, X509_EXTENSION, 1)
} ASN1_SEQUENCE_END(OCSP_SINGLERESP)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_SINGLERESP)

ASN1_SEQUENCE(OCSP_RESPDATA) = {
ASN1_EXP_OPT(OCSP_RESPDATA, version, ASN1_INTEGER, 0),
ASN1_SIMPLE(OCSP_RESPDATA, responderId, OCSP_RESPID),
ASN1_SIMPLE(OCSP_RESPDATA, producedAt, ASN1_GENERALIZEDTIME),
ASN1_SEQUENCE_OF(OCSP_RESPDATA, responses, OCSP_SINGLERESP),
ASN1_EXP_SEQUENCE_OF_OPT(OCSP_RESPDATA, responseExtensions, X509_EXTENSION, 1)
} ASN1_SEQUENCE_END(OCSP_RESPDATA)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_RESPDATA)

ASN1_SEQUENCE(OCSP_BASICRESP) = {
ASN1_SIMPLE(OCSP_BASICRESP, tbsResponseData, OCSP_RESPDATA),
ASN1_SIMPLE(OCSP_BASICRESP, signatureAlgorithm, X509_ALGOR),
ASN1_SIMPLE(OCSP_BASICRESP, signature, ASN1_BIT_STRING),
ASN1_EXP_SEQUENCE_OF_OPT(OCSP_BASICRESP, certs, X509, 0)
} ASN1_SEQUENCE_END(OCSP_BASICRESP)

IMPLEMENT_ASN1_FUNCTIONS(OCSP_BASICRESP)
Loading