|
@@ -15,10 +15,144 @@
|
|
|
#include "lib/tls/tortls.h"
|
|
|
#include "lib/crypt_ops/crypto_rand.h"
|
|
|
#include "lib/crypt_ops/crypto_util.h"
|
|
|
+#include "lib/crypt_ops/crypto_nss_mgt.h"
|
|
|
#include "lib/log/util_bug.h"
|
|
|
+#include "lib/encoding/time_fmt.h"
|
|
|
+#include "lib/string/printf.h"
|
|
|
|
|
|
#include <pk11pub.h>
|
|
|
+#include <cryptohi.h>
|
|
|
#include <cert.h>
|
|
|
+#include <keyhi.h>
|
|
|
+#include <time.h>
|
|
|
+
|
|
|
+/* Units of PRTime per second.
|
|
|
+ *
|
|
|
+ * (PRTime is based in microseconds since the Unix
|
|
|
+ * epoch.) */
|
|
|
+#define PRTIME_PER_SEC (1000*1000)
|
|
|
+
|
|
|
+static tor_x509_cert_impl_t *tor_x509_cert_decode_internal(
|
|
|
+ const uint8_t *certificate, int certificate_len);
|
|
|
+
|
|
|
+static tor_x509_cert_impl_t *
|
|
|
+tor_tls_create_certificate_internal(crypto_pk_t *rsa,
|
|
|
+ crypto_pk_t *rsa_sign,
|
|
|
+ CERTName *subject_dn,
|
|
|
+ CERTName *issuer_dn,
|
|
|
+ time_t start_time,
|
|
|
+ time_t end_time)
|
|
|
+{
|
|
|
+ if (! crypto_pk_key_is_private(rsa_sign)) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ const SECKEYPublicKey *subject_key = crypto_pk_get_nss_pubkey(rsa);
|
|
|
+ const SECKEYPrivateKey *signing_key = crypto_pk_get_nss_privkey(rsa_sign);
|
|
|
+ SECStatus s;
|
|
|
+
|
|
|
+ CERTSubjectPublicKeyInfo *subject_spki = NULL;
|
|
|
+ CERTCertificateRequest *request = NULL;
|
|
|
+ CERTValidity *validity = NULL;
|
|
|
+ CERTCertificate *cert = NULL;
|
|
|
+ SECItem der = { .data = NULL, .len = 0 };
|
|
|
+ SECItem signed_der = { .data = NULL, .len = 0 };
|
|
|
+
|
|
|
+ CERTCertificate *result_cert = NULL;
|
|
|
+
|
|
|
+ validity = CERT_CreateValidity(((PRTime)start_time) * PRTIME_PER_SEC,
|
|
|
+ ((PRTime)end_time) * PRTIME_PER_SEC);
|
|
|
+ if (! validity) {
|
|
|
+ crypto_nss_log_errors(LOG_WARN, "creating a validity object");
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ unsigned long serial_number;
|
|
|
+ crypto_rand((char*)&serial_number, sizeof(serial_number));
|
|
|
+
|
|
|
+ subject_spki = SECKEY_CreateSubjectPublicKeyInfo(subject_key);
|
|
|
+ if (!subject_spki)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ /* Make a CSR ... */
|
|
|
+ // XXX do we need to set any attributes?
|
|
|
+ request = CERT_CreateCertificateRequest(subject_dn,
|
|
|
+ subject_spki,
|
|
|
+ NULL /* attributes */);
|
|
|
+ if (!request)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ /* Put it into a certificate ... */
|
|
|
+ cert = CERT_CreateCertificate(serial_number,
|
|
|
+ issuer_dn,
|
|
|
+ validity,
|
|
|
+ request);
|
|
|
+ if (!cert)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ /* version 3 cert */
|
|
|
+ *cert->version.data = 2; /* 2 means version 3. */
|
|
|
+ cert->version.len = 1;
|
|
|
+
|
|
|
+ // XXX do we need to set anything else on the cert?
|
|
|
+
|
|
|
+ /* Sign it. */
|
|
|
+ KeyType privkey_type = SECKEY_GetPrivateKeyType(signing_key);
|
|
|
+ SECOidTag oid_tag = SEC_GetSignatureAlgorithmOidTag(privkey_type,
|
|
|
+ SEC_OID_SHA256);
|
|
|
+ if (oid_tag == SEC_OID_UNKNOWN)
|
|
|
+ goto err;
|
|
|
+ s = SECOID_SetAlgorithmID(cert->arena, &cert->signature, oid_tag, NULL);
|
|
|
+ if (s != SECSuccess)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ void *tmp;
|
|
|
+ tmp = SEC_ASN1EncodeItem(cert->arena, &der, cert,
|
|
|
+ SEC_ASN1_GET(CERT_CertificateTemplate));
|
|
|
+ if (!tmp)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ s = SEC_DerSignDataWithAlgorithmID(cert->arena,
|
|
|
+ &signed_der,
|
|
|
+ der.data, der.len,
|
|
|
+ (SECKEYPrivateKey *)signing_key,//const
|
|
|
+ &cert->signature);
|
|
|
+
|
|
|
+ if (s != SECSuccess)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ /* Re-parse it, to make sure all the certificates we actually use
|
|
|
+ * appear via being decoded. */
|
|
|
+ result_cert = tor_x509_cert_decode_internal(signed_der.data, signed_der.len);
|
|
|
+
|
|
|
+#if 1
|
|
|
+ {
|
|
|
+ // Can we check the cert we just signed?
|
|
|
+ tor_assert(result_cert);
|
|
|
+ SECKEYPublicKey *issuer_pk = (SECKEYPublicKey *)
|
|
|
+ crypto_pk_get_nss_pubkey(rsa_sign);
|
|
|
+ SECStatus cert_ok = CERT_VerifySignedDataWithPublicKey(
|
|
|
+ &result_cert->signatureWrap, issuer_pk, NULL);
|
|
|
+ tor_assert(cert_ok == SECSuccess);
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ err:
|
|
|
+ if (subject_spki)
|
|
|
+ SECKEY_DestroySubjectPublicKeyInfo(subject_spki);
|
|
|
+ if (request)
|
|
|
+ CERT_DestroyCertificateRequest(request);
|
|
|
+ if (validity)
|
|
|
+ CERT_DestroyValidity(validity);
|
|
|
+
|
|
|
+ // unnecessary, since these are allocated in the cert's arena.
|
|
|
+ //SECITEM_FreeItem(&der, PR_FALSE);
|
|
|
+ //SECITEM_FreeItem(&signed_der, PR_FALSE);
|
|
|
+ if (cert)
|
|
|
+ CERT_DestroyCertificate(cert);
|
|
|
+
|
|
|
+ return result_cert;
|
|
|
+}
|
|
|
|
|
|
MOCK_IMPL(tor_x509_cert_impl_t *,
|
|
|
tor_tls_create_certificate,(crypto_pk_t *rsa,
|
|
@@ -31,9 +165,39 @@ tor_tls_create_certificate,(crypto_pk_t *rsa,
|
|
|
tor_assert(rsa_sign);
|
|
|
tor_assert(cname);
|
|
|
tor_assert(cname_sign);
|
|
|
- (void) cert_lifetime;
|
|
|
- // XXXX
|
|
|
- return NULL;
|
|
|
+
|
|
|
+ char *cname_rfc_1485 = NULL, *cname_sign_rfc_1485 = NULL;
|
|
|
+ CERTName *subject_dn = NULL, *issuer_dn = NULL;
|
|
|
+ time_t start_time;
|
|
|
+ time_t end_time;
|
|
|
+ CERTCertificate *result = NULL;
|
|
|
+
|
|
|
+ tor_asprintf(&cname_rfc_1485, "CN=%s", cname);
|
|
|
+ tor_asprintf(&cname_sign_rfc_1485, "CN=%s", cname_sign);
|
|
|
+
|
|
|
+ subject_dn = CERT_AsciiToName(cname_rfc_1485);
|
|
|
+ issuer_dn = CERT_AsciiToName(cname_sign_rfc_1485);
|
|
|
+ if (!subject_dn || !issuer_dn)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ tor_tls_pick_certificate_lifetime(time(NULL), cert_lifetime,
|
|
|
+ &start_time, &end_time);
|
|
|
+
|
|
|
+ result = tor_tls_create_certificate_internal(rsa,
|
|
|
+ rsa_sign,
|
|
|
+ subject_dn,
|
|
|
+ issuer_dn,
|
|
|
+ start_time,
|
|
|
+ end_time);
|
|
|
+ err:
|
|
|
+ tor_free(cname_rfc_1485);
|
|
|
+ tor_free(cname_sign_rfc_1485);
|
|
|
+ if (subject_dn)
|
|
|
+ CERT_DestroyName(subject_dn);
|
|
|
+ if (issuer_dn)
|
|
|
+ CERT_DestroyName(issuer_dn);
|
|
|
+
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
/** Set *<b>encoded_out</b> and *<b>size_out</b> to <b>cert</b>'s encoded DER
|
|
@@ -63,26 +227,63 @@ tor_x509_cert_t *
|
|
|
tor_x509_cert_dup(const tor_x509_cert_t *cert)
|
|
|
{
|
|
|
tor_assert(cert);
|
|
|
- // XXXX
|
|
|
- return NULL;
|
|
|
+ return tor_x509_cert_new(CERT_DupCertificate(cert->cert));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * As tor_x509_cert_decode, but return the NSS certificate type
|
|
|
+*/
|
|
|
+static tor_x509_cert_impl_t *
|
|
|
+tor_x509_cert_decode_internal(const uint8_t *certificate,
|
|
|
+ int certificate_len)
|
|
|
+{
|
|
|
+ tor_assert(certificate);
|
|
|
+ if (certificate_len > INT_MAX)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ SECItem der = { .type = siBuffer,
|
|
|
+ .data = (unsigned char *)certificate,
|
|
|
+ .len = certificate_len };
|
|
|
+ CERTCertDBHandle *certdb = CERT_GetDefaultCertDB();
|
|
|
+ tor_assert(certdb);
|
|
|
+ return CERT_NewTempCertificate(certdb,
|
|
|
+ &der,
|
|
|
+ NULL /* nickname */,
|
|
|
+ PR_FALSE, /* isPerm */
|
|
|
+ PR_TRUE /* CopyDER */);
|
|
|
}
|
|
|
|
|
|
tor_x509_cert_t *
|
|
|
tor_x509_cert_decode(const uint8_t *certificate,
|
|
|
size_t certificate_len)
|
|
|
{
|
|
|
- tor_assert(certificate);
|
|
|
- (void) certificate_len;
|
|
|
- // XXXX
|
|
|
- return NULL;
|
|
|
+ CERTCertificate *cert = tor_x509_cert_decode_internal(certificate,
|
|
|
+ (int)certificate_len);
|
|
|
+ if (! cert) {
|
|
|
+ crypto_nss_log_errors(LOG_INFO, "decoding certificate");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ tor_x509_cert_t *newcert = tor_x509_cert_new(cert);
|
|
|
+
|
|
|
+ return newcert;
|
|
|
}
|
|
|
|
|
|
crypto_pk_t *
|
|
|
tor_tls_cert_get_key(tor_x509_cert_t *cert)
|
|
|
{
|
|
|
tor_assert(cert);
|
|
|
- // XXXXX
|
|
|
- return NULL;
|
|
|
+ CERTSubjectPublicKeyInfo *spki = &cert->cert->subjectPublicKeyInfo;
|
|
|
+ SECKEYPublicKey *pub = SECKEY_ExtractPublicKey(spki); // we own this pointer
|
|
|
+ if (pub == NULL)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ if (SECKEY_GetPublicKeyType(pub) != rsaKey) {
|
|
|
+ SECKEY_DestroyPublicKey(pub);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return crypto_pk_new_from_nss_pubkey(pub);
|
|
|
}
|
|
|
|
|
|
int
|
|
@@ -92,14 +293,80 @@ tor_tls_cert_is_valid(int severity,
|
|
|
time_t now,
|
|
|
int check_rsa_1024)
|
|
|
{
|
|
|
+ int result = 0;
|
|
|
+
|
|
|
tor_assert(cert);
|
|
|
tor_assert(signing_cert);
|
|
|
- (void)severity;
|
|
|
- (void)now;
|
|
|
- (void)check_rsa_1024;
|
|
|
- // XXXXX
|
|
|
|
|
|
- return 0;
|
|
|
+ SECKEYPublicKey *pk = CERT_ExtractPublicKey(signing_cert->cert);
|
|
|
+ if (pk == NULL) {
|
|
|
+ log_fn(severity, LD_CRYPTO,
|
|
|
+ "Invalid certificate: could not extract issuer key");
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ SECStatus s = CERT_VerifySignedDataWithPublicKey(&cert->cert->signatureWrap,
|
|
|
+ pk, NULL);
|
|
|
+ if (s != SECSuccess) {
|
|
|
+ log_fn(severity, LD_CRYPTO,
|
|
|
+ "Invalid certificate: could not validate signature.");
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tor_x509_check_cert_lifetime_internal(severity,
|
|
|
+ cert->cert,
|
|
|
+ now,
|
|
|
+ TOR_X509_PAST_SLOP,
|
|
|
+ TOR_X509_FUTURE_SLOP) < 0)
|
|
|
+ goto fail;
|
|
|
+
|
|
|
+ if (check_rsa_1024) {
|
|
|
+ /* We require that this is a 1024-bit RSA key, for legacy reasons .:p */
|
|
|
+ if (SECKEY_GetPublicKeyType(pk) != rsaKey ||
|
|
|
+ SECKEY_PublicKeyStrengthInBits(pk) != 1024) {
|
|
|
+ log_fn(severity, LD_CRYPTO, "Invalid certificate: Key is not RSA1024.");
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ /* We require that this key is at least minimally strong. */
|
|
|
+ unsigned min_bits = (SECKEY_GetPublicKeyType(pk) == ecKey) ? 128: 1024;
|
|
|
+ if (SECKEY_PublicKeyStrengthInBits(pk) < min_bits) {
|
|
|
+ log_fn(severity, LD_CRYPTO, "Invalid certificate: Key is too weak.");
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The certificate is valid. */
|
|
|
+ result = 1;
|
|
|
+
|
|
|
+ fail:
|
|
|
+ if (pk)
|
|
|
+ SECKEY_DestroyPublicKey(pk);
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+log_cert_lifetime(int severity,
|
|
|
+ const char *status,
|
|
|
+ time_t now,
|
|
|
+ PRTime notBefore,
|
|
|
+ PRTime notAfter)
|
|
|
+{
|
|
|
+ log_fn(severity, LD_GENERAL,
|
|
|
+ "Certificate %s. Either their clock is set wrong, or your clock "
|
|
|
+ "is incorrect.", status);
|
|
|
+
|
|
|
+ char nowbuf[ISO_TIME_LEN+1];
|
|
|
+ char nbbuf[ISO_TIME_LEN+1];
|
|
|
+ char nabuf[ISO_TIME_LEN+1];
|
|
|
+
|
|
|
+ format_iso_time(nowbuf, now);
|
|
|
+ format_iso_time(nbbuf, notBefore / PRTIME_PER_SEC);
|
|
|
+ format_iso_time(nabuf, notAfter / PRTIME_PER_SEC);
|
|
|
+
|
|
|
+ log_fn(severity, LD_GENERAL,
|
|
|
+ "(The certificate is valid from %s until %s. Your time is %s.)",
|
|
|
+ nbbuf, nabuf, nowbuf);
|
|
|
}
|
|
|
|
|
|
int
|
|
@@ -110,12 +377,33 @@ tor_x509_check_cert_lifetime_internal(int severity,
|
|
|
int future_tolerance)
|
|
|
{
|
|
|
tor_assert(cert);
|
|
|
- (void)severity;
|
|
|
- (void)now;
|
|
|
- (void)past_tolerance;
|
|
|
- (void)future_tolerance;
|
|
|
- // XXXX
|
|
|
- return -1;
|
|
|
+
|
|
|
+ PRTime notBefore=0, notAfter=0;
|
|
|
+ int64_t t;
|
|
|
+ SECStatus r = CERT_GetCertTimes(cert, ¬Before, ¬After);
|
|
|
+ if (r != SECSuccess) {
|
|
|
+ log_fn(severity, LD_CRYPTO,
|
|
|
+ "Couldn't get validity times from certificate");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ t = ((int64_t)now) + future_tolerance;
|
|
|
+ t *= PRTIME_PER_SEC;
|
|
|
+ if (notBefore > t) {
|
|
|
+ log_cert_lifetime(severity, "not yet valid", now,
|
|
|
+ notBefore, notAfter);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ t = ((int64_t)now) - past_tolerance;
|
|
|
+ t *= PRTIME_PER_SEC;
|
|
|
+ if (notAfter < t) {
|
|
|
+ log_cert_lifetime(severity, "already expired", now,
|
|
|
+ notBefore, notAfter);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
#ifdef TOR_UNIT_TESTS
|
|
@@ -126,9 +414,33 @@ tor_x509_cert_replace_expiration(const tor_x509_cert_t *inp,
|
|
|
{
|
|
|
tor_assert(inp);
|
|
|
tor_assert(signing_key);
|
|
|
- (void)new_expiration_time;
|
|
|
|
|
|
- // XXXX
|
|
|
- return NULL;
|
|
|
+ PRTime notBefore=0, notAfter=0;
|
|
|
+ SECStatus r = CERT_GetCertTimes(inp->cert, ¬Before, ¬After);
|
|
|
+ if (r != SECSuccess)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ time_t start_time = notBefore / PRTIME_PER_SEC;
|
|
|
+ if (new_expiration_time < start_time) {
|
|
|
+ /* This prevents an NSS error. */
|
|
|
+ start_time = new_expiration_time - 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ crypto_pk_t *subject_key = tor_tls_cert_get_key((tor_x509_cert_t *)inp);
|
|
|
+ if (!subject_key)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ CERTCertificate *newcert;
|
|
|
+
|
|
|
+ newcert = tor_tls_create_certificate_internal(subject_key,
|
|
|
+ signing_key,
|
|
|
+ &inp->cert->subject,
|
|
|
+ &inp->cert->issuer,
|
|
|
+ start_time,
|
|
|
+ new_expiration_time);
|
|
|
+
|
|
|
+ crypto_pk_free(subject_key);
|
|
|
+
|
|
|
+ return newcert ? tor_x509_cert_new(newcert) : NULL;
|
|
|
}
|
|
|
#endif
|