#ifndef PROTON_SSL_H
#define PROTON_SSL_H 1

/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 <proton/import_export.h>
#include <proton/type_compat.h>
#include <proton/types.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @file
 *
 * @copybrief ssl
 *
 * @addtogroup ssl
 * @{
 */

/**
 * API for using SSL with the Transport Layer.
 *
 * A Transport may be configured to use SSL for encryption and/or authentication.  A
 * Transport can be configured as either an "SSL client" or an "SSL server".  An SSL
 * client is the party that proactively establishes a connection to an SSL server.  An SSL
 * server is the party that accepts a connection request from a remote SSL client.
 *
 * This SSL implementation defines the following objects:

 * @li A top-level object that stores the configuration used by one or more SSL
 * sessions (pn_ssl_domain_t).
 * @li A per-connection SSL session object that performs the encryption/authentication
 * associated with the transport (pn_ssl_t).
 *
 * The pn_ssl_domain_t is used to construct an SSL session (pn_ssl_t).  The
 * session "adopts" its configuration from the pn_ssl_domain_t that was used to create it.
 * For example, pn_ssl_domain_t can be configured as either a "client" or a "server".  SSL
 * sessions constructed from this domain will perform the corresponding role (either
 * client or server).
 *
 * If an SSL session is created without a pn_ssl_domain_t object then a default will be used
 * (see ::pn_ssl_init()).
 *
 * If either an SSL server or client needs to identify itself with the remote node, it
 * must have its SSL certificate configured (see ::pn_ssl_domain_set_credentials()).
 *
 * If either an SSL server or client needs to verify the identity of the remote node, it
 * must have its database of trusted CAs configured. By default this will be set up to use
 * the default system database of trusted CA. But this can be changed
 * (see ::pn_ssl_domain_set_trusted_ca_db()).
 *
 * The level of verification required of the remote may be configured (see
 * ::pn_ssl_domain_set_peer_authentication)
 *
 * Support for SSL Client Session resume is provided (see ::pn_ssl_init,
 * ::pn_ssl_resume_status).
 */
typedef struct pn_ssl_domain_t pn_ssl_domain_t;

/**
 * @see pn_ssl
 */
typedef struct pn_ssl_t pn_ssl_t;

/**
 * Determines the type of SSL endpoint.
 */
typedef enum {
  PN_SSL_MODE_CLIENT = 1, /**< Local connection endpoint is an SSL client */
  PN_SSL_MODE_SERVER      /**< Local connection endpoint is an SSL server */
} pn_ssl_mode_t;

/**
 * Indicates whether an SSL session has been resumed.
 */
typedef enum {
  PN_SSL_RESUME_UNKNOWN,        /**< Session resume state unknown/not supported */
  PN_SSL_RESUME_NEW,            /**< Session renegotiated - not resumed */
  PN_SSL_RESUME_REUSED          /**< Session resumed from previous session. */
} pn_ssl_resume_status_t;

/**
 * Tests for SSL implementation present
 *
 *  @return true if we support SSL, false if not
 */
PN_EXTERN bool pn_ssl_present( void );

/**
 * Create an SSL configuration domain
 *
 * This method allocates an SSL domain object.  This object is used to hold the SSL
 * configuration for one or more SSL sessions.  The SSL session object (pn_ssl_t) is
 * allocated from this object.
 *
 * @param[in] mode the role, client or server, assumed by all SSL sessions created
 * with this domain.
 * @return a pointer to the SSL domain, if SSL support is present.
 */
PN_EXTERN pn_ssl_domain_t *pn_ssl_domain(pn_ssl_mode_t mode);

/**
 * Release an SSL configuration domain
 *
 * This method frees an SSL domain object allocated by ::pn_ssl_domain.
 * @param[in] domain the domain to destroy.
 */
PN_EXTERN void pn_ssl_domain_free(pn_ssl_domain_t *domain);

/**
 * Set the certificate that identifies the local node to the remote.
 *
 * This certificate establishes the identity for the local node for all SSL sessions
 * created from this domain.  It will be sent to the remote if the remote needs to verify
 * the identity of this node.  This may be used for both SSL servers and SSL clients (if
 * client authentication is required by the server).
 *
 * @note This setting effects only those pn_ssl_t objects created after this call
 * returns.  pn_ssl_t objects created before invoking this method will use the domain's
 * previous setting.
 *
 * @param[in] domain the ssl domain that will use this certificate.
 * @param[in] credential_1 specifier for the file/database containing the identifying
 * certificate. For Openssl users, this is a PEM file. For Windows SChannel users, this is
 * the PKCS#12 file or system store.
 * @param[in] credential_2 an optional key to access the identifying certificate. For
 * Openssl users, this is an optional PEM file containing the private key used to sign the
 * certificate. For Windows SChannel users, this is the friendly name of the
 * self-identifying certificate if there are multiple certificates in the store.
 * @param[in] password the password used to sign the key, else NULL if key is not
 * protected.
 * @return 0 on success
 */
PN_EXTERN int  pn_ssl_domain_set_credentials(pn_ssl_domain_t *domain,
                                            const char *credential_1,
                                            const char *credential_2,
                                            const char *password);

/**
 * Configure the set of trusted CA certificates used by this domain to verify peers.
 *
 * If the local SSL client/server needs to verify the identity of the remote, it must
 * validate the signature of the remote's certificate.  This function sets the database of
 * trusted CAs that will be used to verify the signature of the remote's certificate.
 *
 * @note This setting effects only those pn_ssl_t objects created after this call
 * returns.  pn_ssl_t objects created before invoking this method will use the domain's
 * previous setting.
 *
 * @note By default the list of trusted CA certificates will be set to the system default.
 * What this is is depends on the OS and the SSL implementation used: For OpenSSL the default
 * will depend on how the OS is set up. When using the Windows SChannel implementation the default
 * will be the users default trusted certificate store.
 *
 * @param[in] domain the ssl domain that will use the database.
 * @param[in] certificate_db database of trusted CAs, used to authenticate the peer.
 * @return 0 on success
 */
PN_EXTERN int pn_ssl_domain_set_trusted_ca_db(pn_ssl_domain_t *domain,
                                const char *certificate_db);

/**
 * Determines the level of peer validation.
 *
 * ANONYMOUS_PEER does not require a valid certificate, and permits
 * use of ciphers that do not provide authentication.
 *
 * VERIFY_PEER will only connect to those peers that provide a valid
 * identifying certificate signed by a trusted CA and are using an
 * authenticated cipher.
 *
 * VERIFY_PEER_NAME is like VERIFY_PEER, but also requires the peer's
 * identity as contained in the certificate to be valid (see
 * ::pn_ssl_set_peer_hostname).
 *
 * VERIFY_PEER_NAME is configured by default.
 */
typedef enum {
  PN_SSL_VERIFY_NULL = 0,   /**< internal use only */
  PN_SSL_VERIFY_PEER,       /**< require peer to provide a valid identifying certificate */
  PN_SSL_ANONYMOUS_PEER,    /**< do not require a certificate nor cipher authorization */
  PN_SSL_VERIFY_PEER_NAME   /**< require valid certificate and matching name */
} pn_ssl_verify_mode_t;

/**
 * Configure the level of verification used on the peer certificate.
 *
 * This method controls how the peer's certificate is validated, if at all.  By default,
 * servers do not attempt to verify their peers (PN_SSL_ANONYMOUS_PEER) but
 * clients attempt to verify both the certificate and peer name (PN_SSL_VERIFY_PEER_NAME).
 * Once certificates and trusted CAs are configured, peer verification can be enabled.
 *
 * @note In order to verify a peer, a trusted CA must be configured. See
 * ::pn_ssl_domain_set_trusted_ca_db().
 *
 * @note Servers must provide their own certificate when verifying a peer.  See
 * ::pn_ssl_domain_set_credentials().
 *
 * @note This setting effects only those pn_ssl_t objects created after this call
 * returns.  pn_ssl_t objects created before invoking this method will use the domain's
 * previous setting.
 *
 * @param[in] domain the ssl domain to configure.
 * @param[in] mode the level of validation to apply to the peer
 * @param[in] trusted_CAs path to a database of trusted CAs that the server will advertise
 * to the peer client if the server has been configured to verify its peer.
 * @return 0 on success
 */
PN_EXTERN int pn_ssl_domain_set_peer_authentication(pn_ssl_domain_t *domain,
                                                    const pn_ssl_verify_mode_t mode,
                                                    const char *trusted_CAs);

/**
 * Configure the list of permitted TLS protocols
 *
 * @param[in] domain the ssl domain to configure.
 * @param[in] protocols string representing the protocol list.
 * This list is a space separated string of the allowed TLS protocols,
 * The current possibilities are TLSv1 TLSv1.1 TLSv1.2 TLSv1.3. None of the earlier SSL
 * protocols are allowed for security reason.
 *
 * @note If this API not called then all the TLS protocols are allowed. The API only acts to
 * restrict the allowed protocols to the specified set.
 * @return 0 on success
 */
PN_EXTERN int pn_ssl_domain_set_protocols(pn_ssl_domain_t *domain, const char *protocols);

/**
 * Configure the list of permitted ciphers
 *
 * @note The syntax of the permitted list is undefined and will depend on the
 * underlying SSL implementation.
 *
 * @param[in] domain the ssl domain to configure.
 * @param[in] ciphers string representing the cipher list
 * @return 0 on success
 */
PN_EXTERN int pn_ssl_domain_set_ciphers(pn_ssl_domain_t *domain, const char *ciphers);

/**
 * **Deprecated** - Use ::pn_transport_require_encryption()
 *
 * Permit a server to accept connection requests from non-SSL clients.
 *
 * This configures the server to "sniff" the incoming client data stream, and dynamically
 * determine whether SSL/TLS is being used.  This option is disabled by default: only
 * clients using SSL/TLS are accepted.
 *
 * @param[in] domain the domain (server) that will accept the client connections.
 * @return 0 on success
 */
PN_EXTERN int pn_ssl_domain_allow_unsecured_client(pn_ssl_domain_t *domain);

/**
 * Create a new SSL session object associated with a transport.
 *
 * A transport must have an SSL object in order to "speak" SSL over its connection. This
 * method allocates an SSL object associates it with the transport.
 *
 * @param[in] transport the transport that will own the new SSL session.
 * @return a pointer to the SSL object configured for this transport.  Returns NULL if
 * no SSL session is associated with the transport.
 */
PN_EXTERN pn_ssl_t *pn_ssl(pn_transport_t *transport);

/**
 * Initialize an SSL session.
 *
 * This method configures an SSL object with defaults or the configuration provided by the given
 * domain.
 *
 * @note The default client domain is a secure default generally usable. This uses the system default
 * certificates and requires peer name verification.
 *
 * @note However the default server domain is only really useful for test servers. This is because for
 * a useful server you *need* to set up certificates to identify it so that clients can connect without
 * being subject to "man-in-the-middle" attacks - see ::pn_ssl_domain_set_credentials() for details.
 *
 * @param[in] ssl the ssl session to configured.
 * @param[in] domain the ssl domain used to configure the SSL session. If this value is NULL
 * then use a default domain. This will be a client domain if the ssl object is part of a client transport
 * and a server domain if the ssl object is part of a server transport.
 * the transport the pn_ssl_t object
 * @param[in] session_id if supplied, attempt to resume a previous SSL
 * session that used the same session_id.  If no previous SSL session
 * is available, a new session will be created using the session_id
 * and stored for future session restore (see ::::pn_ssl_resume_status).
 * @return 0 on success, else an error code.
 */
PN_EXTERN int pn_ssl_init(pn_ssl_t *ssl,
                          pn_ssl_domain_t *domain,
                          const char *session_id);

/**
 * Get the name of the Cipher that is currently in use.
 *
 * Gets a text description of the cipher that is currently active, or
 * returns FALSE if SSL is not active (no cipher).  Note that the
 * cipher in use may change over time due to renegotiation or other
 * changes to the SSL state.
 *
 * @param[in] ssl the ssl client/server to query.
 * @param[in,out] buffer buffer of size bytes to hold cipher name
 * @param[in] size maximum number of bytes in buffer.
 * @return True if cipher name written to buffer, False if no cipher in use.
 */
PN_EXTERN bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *buffer, size_t size);

/**
 * Get the SSF (security strength factor) of the Cipher that is currently in use.
 *
 * @param[in] ssl the ssl client/server to query.
 * @return the ssf, note that 0 means no security.
 */
PN_EXTERN int pn_ssl_get_ssf(pn_ssl_t *ssl);

/**
 * Get the name of the SSL protocol that is currently in use.
 *
 * Gets a text description of the SSL protocol that is currently active, or returns FALSE if SSL
 * is not active.  Note that the protocol may change over time due to renegotiation.
 *
 * @param[in] ssl the ssl client/server to query.
 * @param[in,out] buffer buffer of size bytes to hold the version identifier
 * @param[in] size maximum number of bytes in buffer.
 * @return True if the version information was written to buffer, False if SSL connection
 * not ready.
 */
PN_EXTERN bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *buffer, size_t size);

/**
 * Check whether the state has been resumed.
 *
 * Used for client session resume.  When called on an active session, indicates whether
 * the state has been resumed from a previous session.
 *
 * @note This is a best-effort service - there is no guarantee that the remote server will
 * accept the resumed parameters.  The remote server may choose to ignore these
 * parameters, and request a re-negotiation instead.
 *
 * @param[in] ssl the ssl session to check
 * @return status code indicating whether or not the session has been resumed.
 */
PN_EXTERN pn_ssl_resume_status_t pn_ssl_resume_status(pn_ssl_t *ssl);

/**
 * Set the expected identity of the remote peer.
 *
 * By default, SSL will use the hostname associated with the connection that
 * the transport is bound to (see ::pn_connection_set_hostname).  This method
 * allows the caller to override that default.
 *
 * The hostname is used for two purposes: 1) when set on an SSL client, it is sent to the
 * server during the handshake (if Server Name Indication is supported), and 2) it is used
 * to check against the identifying name provided in the peer's certificate. If the
 * supplied name does not exactly match a SubjectAltName (type DNS name), or the
 * CommonName entry in the peer's certificate, the peer is considered unauthenticated
 * (potential imposter), and the SSL connection is aborted.
 *
 * @note Verification of the hostname is only done if PN_SSL_VERIFY_PEER_NAME is enabled.
 * See ::pn_ssl_domain_set_peer_authentication.
 *
 * @param[in] ssl the ssl session.
 * @param[in] hostname the expected identity of the remote. Must conform to the syntax as
 * given in RFC1034, Section 3.5.
 * @return 0 on success.
 */
PN_EXTERN int pn_ssl_set_peer_hostname(pn_ssl_t *ssl, const char *hostname);

/**
 * Access the configured peer identity.
 *
 * Return the expected identity of the remote peer, as set by ::pn_ssl_set_peer_hostname.
 *
 * @param[in] ssl the ssl session.
 * @param[out] hostname buffer to hold the null-terminated name string. If null, no string
 * is written.
 * @param[in,out] bufsize on input set to the number of octets in hostname. On output, set
 * to the number of octets needed to hold the value of hostname plus a null byte.  Zero if
 * no hostname set.
 * @return 0 on success.
 */
PN_EXTERN int pn_ssl_get_peer_hostname(pn_ssl_t *ssl, char *hostname, size_t *bufsize);

/**
 * Get the subject from the peers certificate.
 *
 * @param[in] ssl the ssl client/server to query.
 * @return A null terminated string representing the full subject,
 * which is valid until the ssl object is destroyed.
 */
PN_EXTERN const char* pn_ssl_get_remote_subject(pn_ssl_t *ssl);

/**
 * Enumeration identifying the sub fields of the subject field in the ssl certificate.
 */
typedef enum {
  PN_SSL_CERT_SUBJECT_COUNTRY_NAME,
  PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE,
  PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY,
  PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME,
  PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT,
  PN_SSL_CERT_SUBJECT_COMMON_NAME
} pn_ssl_cert_subject_subfield;

/**
 * Enumeration identifying hashing algorithm.
 */
typedef enum {
  PN_SSL_SHA1,   /* Produces hash that is 20 bytes long */
  PN_SSL_SHA256, /* Produces hash that is 32 bytes long */
  PN_SSL_SHA512, /* Produces hash that is 64 bytes long */
  PN_SSL_MD5     /* Produces hash that is 16 bytes long */
} pn_ssl_hash_alg;

/**
 * Get the fingerprint of the certificate. The certificate fingerprint (as displayed in the Fingerprints section when
 * looking at a certificate with say the Firefox browser) is the hexadecimal hash of the entire certificate.
 * The fingerprint is not part of the certificate, rather it is computed from the certificate and can be used to uniquely identify a certificate.
 * @param[in] ssl0 the ssl client/server to query
 * @param[in] fingerprint char pointer. The certificate fingerprint (in hex format) will be populated in this array.
 *            If sha1 is the digest name, the fingerprint is 41 characters long (40 + 1 '\0' character), 65 characters long for
 *            sha256 and 129 characters long for sha512 and 33 characters for md5.
 * @param[in] fingerprint_length - Must be at >= 33 for md5, >= 41 for sha1, >= 65 for sha256 and >=129 for sha512.
 * @param[in] hash_alg the hash algorithm to use. Must be of type pn_ssl_hash_alg (currently supports sha1, sha256, sha512 and md5)
 * @return error code - Returns 0 on success. Return a value less than zero if there were any errors. Upon execution of this function,
 *                      char *fingerprint will contain the appropriate null terminated hex fingerprint
 */
PN_EXTERN int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl0,
                                          char *fingerprint,
                                          size_t fingerprint_length,
                                          pn_ssl_hash_alg hash_alg);

/**
 * Returns a char pointer that contains the value of the sub field of the subject field in the ssl certificate. The subject field usually contains the following sub fields -
 * C = ISO3166 two character country code
 * ST = state or province
 * L = Locality; generally means city
 * O = Organization - Company Name
 * OU = Organization Unit - division or unit
 * CN = CommonName
 * @param[in] ssl0 the ssl client/server to query
 * @param[in] field The enumeration pn_ssl_cert_subject_subfield representing the required sub field.
 * @return A null terminated string which contains the requested sub field value which is valid until the ssl object is destroyed.
 */
PN_EXTERN const char* pn_ssl_get_remote_subject_subfield(pn_ssl_t *ssl0, pn_ssl_cert_subject_subfield field);

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* ssl.h */
