diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9fe7d44b..6371b2c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+
## Useful Gists related to VaultSharp usage
* Helper code to get Kerberos Negotiation Token using keytab and krb5 config file: https://gist.github.com/rajanadar/28c86d967695262bfe1f17ae82fb3d3d
@@ -10,6 +11,7 @@
* auth/azure: Add the ```resource_id``` parameter to the login method.
* [GH-365](https://github.com/rajanadar/VaultSharp/issues/365) Add new ```transit key type``` values like ```hmac-key```, ```cmac-key``` etc.
* [GH-366](https://github.com/rajanadar/VaultSharp/issues/366) PKI Secrets Engine - return raw cert data, including revocation date
+ * [GH-373](https://github.com/rajanadar/VaultSharp/issues/373) PKI Secrets Engine - added methods for interacting with PKI Roles, and method for generating a self-signed root CA.
## 1.17.5.1 (September 18, 2024)
diff --git a/README.md b/README.md
index f0c01cc3..68b53162 100644
--- a/README.md
+++ b/README.md
@@ -1304,6 +1304,59 @@ var caCert = await vaultClient.V1.Secrets.PKI.ReadCACertificateAsync(Certificate
Assert.NotNull(caCert.CertificateContent);
```
+##### Generate Root
+ - This endpoint generates a new self-signed CA certificate and private key. If the 'type' is `exported`, the private key will be returned in the response;
+ - if it is `internal` the private key will not be returned and cannot be retrieved later;
+ - if it is `existing`, the key specified by key_ref will be reused for this root;
+ - if it is `kms`, a managed key will be used.
+
+ ```cs
+var newRoot = new GenerateRootRequest()
+{
+ CommonName = "test.com",
+ IssuerName = "testroot_test_com",
+ KeyName = "testroot_key",
+};
+var type = "exported";
+var caCert = await vaultClient.V1.Secrets.PKI.GenerateRootAsync(type, newRoot, mountpoint)
+Assert.NotEmpty(newRootResponse.Data.PrivateKey);
+```
+
+##### List Roles
+
+ - This endpoint lists all of the role names that have been created for the PKI secrets engine.
+
+```cs
+var roleNames = await vaultClient.V1.Secrets.PKI.ListRolesAsync(mountpoint);
+Assert.IsList(roleNames);
+```
+
+##### Read Role
+
+ - This endpoint retreives the details for a role with the specified name.
+
+```cs
+var roleDetails = await vaultClient.V1.Secrets.PKI.ReadRoleAsync(roleName, mountpoint);
+```
+
+##### Write Role
+
+ - This endpoint writes a new role definition
+ - If the role name already exists, it will be completely overwritten
+
+```cs
+var roleDetails = await vaultClient.V1.Secrets.PKI.ReadRoleAsync(roleName, roleDetails, mountpoint);
+```
+
+##### Patch Role
+ - This endpoint will update an existing role with provided values
+ - Null values in the request will not overwritten existing values in the role definition
+
+```cs
+var updatedRoleDetails = await vaultClient.V1.Secrets.PKI.PatchRoleAsync(roleName, roleDetails, mountpoint);
+```
+
+
#### RabbitMQ Secrets Engine
##### Generate dynamic DB credentials
diff --git a/src/VaultSharp/V1/SecretsEngines/PKI/GenerateRootRequest.cs b/src/VaultSharp/V1/SecretsEngines/PKI/GenerateRootRequest.cs
new file mode 100644
index 00000000..291ac2ea
--- /dev/null
+++ b/src/VaultSharp/V1/SecretsEngines/PKI/GenerateRootRequest.cs
@@ -0,0 +1,218 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace VaultSharp.V1.SecretsEngines.PKI
+{
+ public class GenerateRootRequest
+ {
+ ///
+ /// The requested Subject Alternative Names, if any, in a comma-delimited list. May contain both DNS names and email addresses.
+ ///
+ [JsonPropertyName("alt_names")]
+ public string AltNames { get; set; }
+
+ ///
+ /// The requested common name; if you want more than one, specify the alternative names in the alt_names map. If not specified when signing,
+ /// the common name will be taken from the CSR; other names must still be specified in alt_names or ip_sans.
+ ///
+ [JsonPropertyName("common_name")]
+ public string CommonName { get; set; }
+
+ ///
+ /// If set, Country will be set to this value.
+ ///
+ [JsonPropertyName("country")]
+ public List Country { get; set; }
+
+ ///
+ /// If true, the Common Name will not be included in DNS or Email Subject Alternate Names. Defaults to false (CN is included).
+ ///
+ [JsonPropertyName("exclude_cn_from_sans")]
+ public bool ExcludeCNFromSans { get; set; }
+
+ ///
+ /// Format for returned data. Can be "pem", "der", or "pem_bundle". If "pem_bundle", any private key and issuing cert will be appended to the certificate pem.
+ /// If "der", the value will be base64 encoded.
+ /// Defaults to "pem".
+ ///
+ [JsonPropertyName("format")]
+ public string Format { get; set; }
+
+ ///
+ /// The requested IP SANs, if any
+ ///
+ [JsonPropertyName("ip_sans")]
+ public List IpSans { get; set; }
+
+ ///
+ /// Provide a name to the generated or existing issuer, the name must be unique across all issuers and not be the reserved value 'default'
+ ///
+ [JsonPropertyName("issuer_name")]
+ public string IssuerName { get; set; }
+
+ ///
+ /// The number of bits to use. Allowed values are 0 (universal default);
+ /// with rsa key_type: 2048 (default), 3072, 4096 or 8192;
+ /// with ec key_type: 224, 256 (default), 384, or 521;
+ /// ignored with ed25519.
+ ///
+ [JsonPropertyName("key_bits")]
+ public int KeyBits { get; set; }
+
+ ///
+ /// Provide a name to the generated or existing key, the name must be unique across all keys and not be the reserved value 'default'
+ ///
+ [JsonPropertyName("key_name")]
+ public string KeyName { get; set; }
+
+ ///
+ /// Reference to a existing key; either "default" for the configured default key, an identifier or the name assigned to the key.
+ ///
+ [JsonPropertyName("key_ref")]
+ public string KeyRef { get; set; }
+
+ ///
+ /// The type of key to use;
+ /// defaults to RSA.
+ /// "rsa" "ec" and "ed25519" are the only valid values.
+ ///
+ [JsonPropertyName("key_type")]
+ public string KeyType { get; set; }
+
+ ///
+ /// If set, Locality will be set to this value.
+ ///
+ [JsonPropertyName("locality")]
+ public List Locality { get; set; }
+
+ ///
+ /// The name of the managed key to use when the exported type is kms. When kms type is the key type, this field or managed_key_name is required.
+ /// Ignored for other types.
+ ///
+ [JsonPropertyName("managed_key_id")]
+ public string ManagedKeyId { get; set; }
+
+ ///
+ /// The name of the managed key to use when the exported type is kms. When kms type is the key type, this field or managed_key_id is required.
+ /// Ignored for other types.
+ ///
+ [JsonPropertyName("managed_key_name")]
+ public string ManagedKeyName { get; set; }
+
+ ///
+ /// The maximum allowable path length
+ ///
+ [JsonPropertyName("max_path_length")]
+ public int MaxPathLength { get; set; }
+
+ ///
+ /// Set the not after field of the certificate with specified date value.
+ /// The value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ
+ ///
+ [JsonPropertyName("not_after")]
+ public string NotAfter { get; set; }
+
+ ///
+ /// The duration before now which the certificate needs to be backdated by.
+ ///
+ [JsonPropertyName("not_before_duration")]
+ public string NotBeforeDuration { get; set; }
+
+ ///
+ /// If set, O (Organization) will be set to this value.
+ ///
+ [JsonPropertyName("organization")]
+ public List Organization { get; set; }
+
+ ///
+ /// Requested other SANs, in an array with the format ;UTF8: for each entry.
+ ///
+ [JsonPropertyName("other_sans")]
+ public List OtherSans { get; set; }
+
+ ///
+ /// If set, OU (OrganizationalUnit) will be set to this value.
+ ///
+ [JsonPropertyName("ou")]
+ public List OrganizationalUnit { get; set; }
+
+ ///
+ /// Domains for which this certificate is allowed to sign or issue child certificates.
+ /// If set, all DNS names (subject and alt) on child certs must be exact matches or subsets of the given domains
+ /// (see https://tools.ietf.org/html/rfc5280#section-4.2.1.10).
+ ///
+ [JsonPropertyName("permitted_dns_domains")]
+ public List PermittedDnsDomains { get; set; }
+
+ ///
+ /// If set, Postal Code will be set to this value.
+ ///
+ [JsonPropertyName("postal_code")]
+ public List PostalCode { get; set; }
+
+ ///
+ /// Format for the returned private key.
+ /// Generally the default will be controlled by the "format" parameter as either base64-encoded DER or PEM-encoded DER.
+ /// However, this can be set to "pkcs8" to have the returned private key contain base64-encoded pkcs8 or PEM-encoded pkcs8 instead.
+ /// Defaults to "der".
+ ///
+ [JsonPropertyName("private_key_format")]
+ public string PrivateKeyFormat { get; set; }
+
+ ///
+ /// If set, Province will be set to this value.
+ ///
+ [JsonPropertyName("province")]
+ public List Province { get; set; }
+
+ ///
+ /// The Subject's requested serial number, if any. See RFC 4519 Section 2.31 'serialNumber' for a description of this field.
+ /// If you want more than one, specify alternative names in the alt_names map using OID 2.5.4.5.
+ /// This has no impact on the final certificate's Serial Number field.
+ ///
+ [JsonPropertyName("serial_number")]
+ public string SerialNumber { get; set; }
+
+ ///
+ /// The number of bits to use in the signature algorithm; accepts 256 for SHA-2-256, 384 for SHA-2-384, and 512 for SHA-2-512.
+ /// Defaults to 0 to automatically detect based on key length (SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).
+ ///
+ [JsonPropertyName("signature_bits")]
+ public int SignatureBits { get; set; }
+
+ ///
+ /// If set, Street Address will be set to this value.
+ ///
+ [JsonPropertyName("street_address")]
+ public List StreetAddress { get; set; }
+
+ ///
+ /// The requested Time To Live for the certificate; sets the expiration date.
+ /// If not specified the role default, backend default, or system default TTL is used, in that order. Cannot be larger than the mount max TTL.
+ /// Note: this only has an effect when generating a CA cert or signing a CA cert, not when generating a CSR for an intermediate CA.
+ ///
+ [JsonPropertyName("ttl")]
+ public string TTL { get; set; }
+
+ ///
+ /// The requested URI SANs, if any, in a comma-delimited list.
+ ///
+ [JsonPropertyName("uri_sans")]
+ public List UriSans { get; set; }
+
+ ///
+ /// Whether or not to use PSS signatures when using a RSA key-type issuer. Defaults to false.
+ ///
+ [JsonPropertyName("use_pss")]
+ public bool UsePss { get; set; }
+
+ public GenerateRootRequest()
+ {
+ //set defaults
+ Format = "pem";
+ KeyType = "rsa";
+ IssuerName = string.Empty;
+ KeyName = string.Empty;
+ }
+ }
+}
diff --git a/src/VaultSharp/V1/SecretsEngines/PKI/GenerateRootResponse.cs b/src/VaultSharp/V1/SecretsEngines/PKI/GenerateRootResponse.cs
new file mode 100644
index 00000000..16c7af9e
--- /dev/null
+++ b/src/VaultSharp/V1/SecretsEngines/PKI/GenerateRootResponse.cs
@@ -0,0 +1,61 @@
+using System.Text.Json.Serialization;
+
+namespace VaultSharp.V1.SecretsEngines.PKI
+{
+ public class GenerateRootResponse
+ {
+ ///
+ /// The generated self-signed CA certificate.
+ ///
+ [JsonPropertyName("certificate")]
+ public string Certificate { get; set; }
+
+ ///
+ /// The expiration of the given issuer.
+ ///
+ [JsonPropertyName("expiration")]
+ public long Expiration { get; set; }
+
+ ///
+ /// The ID of the issuer
+ ///
+ [JsonPropertyName("issuer_id")]
+ public string IssuerId { get; set; }
+
+ ///
+ /// The name of the issuer.
+ ///
+ [JsonPropertyName("issuer_name")]
+ public string IssuerName { get; set; }
+
+ ///
+ /// The issuing certificate authority.
+ ///
+ [JsonPropertyName("issuing_ca")]
+ public string IssuingCA { get; set; }
+
+ ///
+ /// The ID of the key.
+ ///
+ [JsonPropertyName("key_id")]
+ public string KeyId { get; set; }
+
+ ///
+ /// The key name if given.
+ ///
+ [JsonPropertyName("key_name")]
+ public string KeyName { get; set; }
+
+ ///
+ /// The private key if exported was specified
+ ///
+ [JsonPropertyName("private_key")]
+ public string PrivateKey { get; set; }
+
+ ///
+ /// The requested Subject's named serial number.
+ ///
+ [JsonPropertyName("serial_number")]
+ public string SerialNumber { get; set; }
+ }
+}
diff --git a/src/VaultSharp/V1/SecretsEngines/PKI/IPKISecretsEngine.cs b/src/VaultSharp/V1/SecretsEngines/PKI/IPKISecretsEngine.cs
index 0a3fca95..0ae8e894 100644
--- a/src/VaultSharp/V1/SecretsEngines/PKI/IPKISecretsEngine.cs
+++ b/src/VaultSharp/V1/SecretsEngines/PKI/IPKISecretsEngine.cs
@@ -200,5 +200,90 @@ public interface IPKISecretsEngine
/// The secret with the certificate chain data
///
Task> ReadDefaultIssuerCertificateChainAsync(CertificateFormat certificateFormat, string pkiBackendMountPoint = null);
+
+ ///
+ /// Generates a new self-signed CA certificate and private key. If the path ends with exported, the private key will be returned in the response;
+ /// if it is internal the private key will not be returned and cannot be retrieved later;
+ /// if it is existing, the key specified by key_ref will be reused for this root;
+ /// if it is kms, a managed key will be used.
+ ///
+ /// valid values are "exported", "internal", and "existing"
+ /// The configuration values to use for the newly generated root certificate
+ ///
+ /// The mount point for the PKI backend. Defaults to
+ /// Provide a value only if you have customized the PKI mount point.
+ ///
+ /// The secret with the generated root response
+ /// if the type was "exported", the private key will be included.
+ ///
+ Task> GenerateRootAsync(string type, GenerateRootRequest generateRootRequest, string pkiBackendMountPoint = null);
+
+ ///
+ /// Retrieves a list of all PKI role names.
+ ///
+ ///
+ /// The mount point for the PKI backend. Defaults to
+ /// Provide a value only if you have customized the PKI mount point.
+ ///
+ ///
+ /// The secret with the list of role names
+ ///
+ Task> ListRolesAsync(string pkiBackendMountPoint = null);
+
+ ///
+ /// Retrieves details for a role by name.
+ ///
+ ///
+ /// The name of the role to be retrieved.
+ ///
+ ///
+ /// The mount point for the PKI backend. Defaults to
+ /// Provide a value only if you have customized the PKI mount point.
+ ///
+ ///
+ /// The secret with the role data
+ ///
+ Task> ReadRoleAsync(string pkiRoleName, string pkiBackendMountPoint = null);
+
+ ///
+ /// Writes a role definition with the provided pkiRoleName.
+ /// if the role already exists, it will overwrite it with all values (including elided parameters)
+ /// To update a role with only the provided parameters, use 'PatchRoleAsync'
+ ///
+ ///
+ /// The name of the role to be retrieved.
+ ///
+ ///
+ /// The PKIRole object containing the values.
+ ///
+ ///
+ /// The mount point for the PKI backend. Defaults to
+ /// Provide a value only if you have customized the PKI mount point.
+ ///
+ ///
+ /// The secret with the role data
+ ///
+ Task> WriteRoleAsync(string pkiRoleName, PKIRole pkiRoleDetails, string pkiBackendMountPoint = null);
+
+ ///
+ /// Patches an existing role definition with the provided pkiRoleName.
+ /// Use this method for updating (not replacing) a role definition with the non-null values provided in the pkiRoleDetails object.
+ /// This method requires the role definition to already exist, and will update it with any non-null values set in pkiRoleDetails.
+ /// To replace a role definition, use the 'WriteRoleAsync' method.
+ ///
+ ///
+ /// The name of the role to be retrieved.
+ ///
+ ///
+ /// The PKIRole object containing the values.
+ ///
+ ///
+ /// The mount point for the PKI backend. Defaults to
+ /// Provide a value only if you have customized the PKI mount point.
+ ///
+ ///
+ /// The secret with the role data
+ ///
+ Task> PatchRoleAsync(string pkiRoleName, PKIRole pkiRoleDetails, string pkiBackendMountPoint = null);
}
}
\ No newline at end of file
diff --git a/src/VaultSharp/V1/SecretsEngines/PKI/PKIRole.cs b/src/VaultSharp/V1/SecretsEngines/PKI/PKIRole.cs
new file mode 100644
index 00000000..33aa4855
--- /dev/null
+++ b/src/VaultSharp/V1/SecretsEngines/PKI/PKIRole.cs
@@ -0,0 +1,322 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace VaultSharp.V1.SecretsEngines.PKI
+{
+ public class PKIRole
+ {
+ ///
+ /// If set, clients can request certificates for any domain, regardless of allowed_domains restrictions. See the documentation for more information.
+ ///
+ [JsonPropertyName("allow_any_name")]
+ public bool AllowAnyName { get; set; }
+
+ ///
+ /// If set, clients can request certificates for the base domains themselves, e.g. "example.com" of domains listed in allowed_domains.
+ /// This is a separate option as in some cases this can be considered a security threat. See the documentation for more information.
+ ///
+ [JsonPropertyName("allow_bare_domains")]
+ public bool AllowBareDomains { get; set; }
+
+ ///
+ /// If set, domains specified in allowed_domains can include shell-style glob patterns, e.g. "ftp*.example.com".
+ /// See the documentation for more information.
+ ///
+ [JsonPropertyName("allow_glob_domains")]
+ public bool AllowGlobDomains { get; set; }
+
+ ///
+ /// If set, IP Subject Alternative Names are allowed. Any valid IP is accepted and No authorization checking is performed.
+ ///
+ [JsonPropertyName("allow_ip_sans")]
+ public bool AllowIpSans { get; set; }
+
+ ///
+ /// Whether to allow "localhost" and "localdomain" as a valid common name in a request, independent of allowed_domains value.
+ ///
+ [JsonPropertyName("allow_localhost")]
+ public bool AllowLocalhost { get; set; }
+
+ ///
+ /// If set, clients can request certificates for subdomains of domains listed in allowed_domains, including wildcard subdomains.
+ /// See the documentation for more information.
+ ///
+ [JsonPropertyName("allow_subdomains")]
+ public bool AllowSubdomains { get; set; }
+
+ ///
+ /// If set, allows certificates with wildcards in the common name to be issued, conforming to RFC 6125's Section 6.4.3; e.g.,
+ /// ".example.net" or "bz.example.net". See the documentation for more information.
+ ///
+ [JsonPropertyName("allow_wildcard_certificates")]
+ public bool AllowWildcardIdentifiers { get; set; }
+
+ ///
+ /// Specifies the domains this role is allowed to issue certificates for. This is used with the allow_bare_domains, allow_subdomains,
+ /// and allow_glob_domains to determine matches for the common name, DNS-typed SAN entries, and Email-typed SAN entries of certificates.
+ /// See the documentation for more information. This parameter accepts a comma-separated string or list of domains.
+ ///
+ [JsonPropertyName("allowed_domains")]
+ public List AllowedDomains { get; set; }
+
+ ///
+ /// If set, Allowed domains can be specified using identity template policies. Non-templated domains are also permitted.
+ ///
+ [JsonPropertyName("allowed_domains_template")]
+ public bool AllowedDomainsTemplate { get; set; }
+
+ ///
+ /// If set, an array of allowed serial numbers to put in Subject. These values support globbing.
+ ///
+ [JsonPropertyName("allowed_serial_numbers")]
+ public List AllowedSerialNumbers { get; set; }
+
+ ///
+ /// If set, an array of allowed URIs for URI Subject Alternative Names. Any valid URI is accepted, these values support globbing.
+ ///
+ [JsonPropertyName("allowed_uri_sans")]
+ public List AllowedUriSans { get; set; }
+
+ ///
+ /// If set, Allowed URI SANs can be specified using identity template policies. Non-templated URI SANs are also permitted.
+ ///
+ [JsonPropertyName("allowed_uri_sans_template")]
+ public bool AllowedUriSansTemplate { get; set; }
+
+ ///
+ /// If set, an array of allowed user-ids to put in user system login name specified here: https://www.rfc-editor.org/rfc/rfc1274#section-9.3.1
+ ///
+ [JsonPropertyName("allowed_user_ids")]
+ public List AllowedUserIds { get; set; }
+
+ ///
+ /// Mark Basic Constraints valid when issuing non-CA certificates.
+ ///
+ [JsonPropertyName("basic_constraints_valid_for_non_ca")]
+ public bool BasicConstraintsValidForNonCA { get; set; }
+
+ ///
+ /// If set, certificates are flagged for client auth use. Defaults to true. See also RFC 5280 Section 4.2.1.12.
+ ///
+ [JsonPropertyName("client_flag")]
+ public bool ClientFlag { get; set; }
+
+ ///
+ /// List of allowed validations to run against the Common Name field. Values can include 'email' to validate the CN is a email address,
+ /// 'hostname' to validate the CN is a valid hostname (potentially including wildcards). When multiple validations are specified,
+ /// these take OR semantics (either email OR hostname are allowed). The special value 'disabled' allows disabling all CN name validations,
+ /// allowing for arbitrary non-Hostname, non-Email address CNs.
+ ///
+ [JsonPropertyName("cn_validations")]
+ public List CNVAlidations { get; set; }
+
+ ///
+ /// If set, certificates are flagged for code signing use. Defaults to false. See also RFC 5280 Section 4.2.1.12.
+ ///
+ [JsonPropertyName("code_signing_flag")]
+ public bool CodeSigningFlag { get; set; }
+
+ ///
+ /// If set, Country will be set to this value in certificates issued by this role.
+ ///
+ [JsonPropertyName("country")]
+ public List Country { get; set; }
+
+ ///
+ /// If set, certificates are flagged for email protection use.
+ /// Defaults to false.
+ /// See also RFC 5280 Section 4.2.1.12.
+ ///
+ [JsonPropertyName("email_protection_flag")]
+ public bool EmailProtectionFlag { get; set; }
+
+ ///
+ /// If set, only valid host names are allowed for CN and DNS SANs, and the host part of email addresses.
+ /// Defaults to true.
+ ///
+ [JsonPropertyName("enforce_hostnames")]
+ public bool EnforceHostnames { get; set; }
+
+ ///
+ /// A comma-separated string or list of extended key usages. Valid values can be found at https://golang.org/pkg/crypto/x509/#ExtKeyUsage --
+ /// simply drop the "ExtKeyUsage" part of the name. To remove all key usages from being set, set this value to an empty list.
+ /// See also RFC 5280 Section 4.2.1.12.
+ ///
+ [JsonPropertyName("ext_key_usage")]
+ public List ExtKeyUsage { get; set; }
+
+ ///
+ /// If set, certificates issued/signed against this role will have Vault leases attached to them. Defaults to "false".
+ /// Certificates can be added to the CRL by "vault revoke " when certificates are associated with leases.
+ /// It can also be done using the "pki/revoke" endpoint. However, when lease generation is disabled, invoking "pki/revoke" would be the only
+ /// way to add the certificates to the CRL. When large number of certificates are generated with long lifetimes, it is recommended that lease generation be disabled,
+ /// as large amount of leases adversely affect the startup time of Vault.
+ ///
+ [JsonPropertyName("generate_lease")]
+ public bool GenerateLease { get; set; }
+
+ ///
+ /// Reference to the issuer used to sign requests serviced by this role.
+ ///
+ [JsonPropertyName("issuer_ref")]
+ public string IssuerRef { get; set; }
+
+ ///
+ /// The number of bits to use. Allowed values are:
+ /// 0 (universal default);
+ /// with rsa key_type: 2048 (default), 3072, or 4096;
+ /// with ec key_type: 224, 256 (default), 384, or 521;
+ /// ignored with ed25519.
+ ///
+ [JsonPropertyName("key_bits")]
+ public int KeyBits { get; set; }
+
+ ///
+ /// The type of key to use;
+ /// defaults to RSA.
+ /// "rsa" "ec", "ed25519" and "any" are the only valid values.
+ ///
+ [JsonPropertyName("key_type")]
+ public string KeyType { get; set; }
+
+ ///
+ /// A comma-separated string or list of key usages (not extended key usages).
+ /// Valid values can be found at https://golang.org/pkg/crypto/x509/#KeyUsage -- simply drop the "KeyUsage" part of the name.
+ /// To remove all key usages from being set, set this value to an empty list. See also RFC 5280 Section 4.2.1.3.
+ ///
+ [JsonPropertyName("key_usage")]
+ public List KeyUsage { get; set; }
+
+ ///
+ /// If set, Locality will be set to this value in certificates issued by this role.
+ ///
+ [JsonPropertyName("locality")]
+ public List Locality { get; set; }
+
+ ///
+ /// The maximum allowed lease duration. If not set, defaults to the system maximum lease TTL.
+ ///
+ [JsonPropertyName("max_ttl")]
+ public long MaxTTL { get; set; }
+
+ ///
+ /// If set, certificates issued/signed against this role will not be stored in the storage backend.
+ /// This can improve performance when issuing large numbers of certificates. However, certificates issued in this way cannot be enumerated or revoked,
+ /// so this option is recommended only for certificates that are non-sensitive, or extremely short-lived.
+ /// This option implies a value of "false" for "generate_lease".
+ ///
+ [JsonPropertyName("no_store")]
+ public bool NoStore { get; set; }
+
+ ///
+ /// If set, if a client attempts to issue or sign a certificate with attached cert_metadata to store, the issuance / signing instead fails.
+ ///
+ [JsonPropertyName("no_store_metadata")]
+ public bool NoStoreMetadata { get; set; }
+
+ ///
+ /// Set the not after field of the certificate with specified date value. The value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ.
+ ///
+ [JsonPropertyName("not_after")]
+ public string NotAfter { get; set; }
+
+ ///
+ /// The duration in seconds before now which the certificate needs to be backdated by.
+ ///
+ [JsonPropertyName("not_before_duration")]
+ public long NotBeforeDuration { get; set; }
+
+ ///
+ /// If set, O (Organization) will be set to this value in certificates issued by this role.
+ ///
+ [JsonPropertyName("organization")]
+ public List Organization { get; set; }
+
+ ///
+ /// If set, OU (OrganizationalUnit) will be set to this value in certificates issued by this role.
+ ///
+ [JsonPropertyName("ou")]
+ public List OrganizationalUnit { get; set; }
+
+ ///
+ /// A comma-separated string or list of policy OIDs, or a JSON list of qualified policy information,
+ /// which must include an oid, and may include a notice and/or cps url,
+ /// using the form [{"oid"="1.3.6.1.4.1.7.8","notice"="I am a user Notice"}, {"oid"="1.3.6.1.4.1.44947.1.2.4 ","cps"="https://example.com"}].
+ ///
+ [JsonPropertyName("policy_identifiers")]
+ public List PolicyIdentifiers { get; set; }
+
+ ///
+ /// If set, Postal Code will be set to this value in certificates issued by this role.
+ ///
+ [JsonPropertyName("postal_code")]
+ public List PostalCode { get; set; }
+
+ ///
+ /// If set, Province will be set to this value in certificates issued by this role.
+ ///
+ [JsonPropertyName("province")]
+ public List Province { get; set; }
+
+ ///
+ /// If set to false, makes the 'common_name' field optional while generating a certificate.
+ ///
+ [JsonPropertyName("require_cn")]
+ public bool RequireCN { get; set; }
+
+ ///
+ /// If set, certificates are flagged for server auth use. Defaults to true. See also RFC 5280 Section 4.2.1.12.
+ ///
+ [JsonPropertyName("server_flag")]
+ public bool ServerFlag { get; set; }
+
+ ///
+ /// The number of bits to use in the signature algorithm; accepts 256 for SHA-2-256, 384 for SHA-2-384, and 512 for SHA-2-512.
+ /// Defaults to 0 to automatically detect based on key length (SHA-2-256 for RSA keys, and matching the curve size for NIST P-Curves).
+ ///
+ [JsonPropertyName("signature_bits")]
+ public int SignatureBits { get; set; }
+
+ ///
+ /// If set, Street Address will be set to this value in certificates issued by this role.
+ ///
+ [JsonPropertyName("street_address")]
+ public List StreetAddress { get; set; }
+
+ ///
+ /// The lease duration (validity period of the certificate) if no specific lease duration is requested.
+ /// The lease duration controls the expiration of certificates issued by this backend. Defaults to the system default value or the value of max_ttl,
+ /// whichever is shorter.
+ ///
+ [JsonPropertyName("ttl")]
+ public long TTL { get; set; }
+
+ ///
+ /// If set, when used with a signing profile, the common name in the CSR will be used. This does not include any requested Subject Alternative Names;
+ /// use use_csr_sans for that.
+ /// Defaults to true.
+ ///
+ [JsonPropertyName("use_csr_common_name")]
+ public bool UseCsrCommonName { get; set; }
+
+ ///
+ /// If set, when used with a signing profile, the SANs in the CSR will be used. This does not include the Common Name (cn); use use_csr_common_name for that.
+ /// Defaults to true.
+ ///
+ [JsonPropertyName("use_csr_sans")]
+ public bool UseCsrSans { get; set; }
+
+ ///
+ /// Whether or not to use PSS signatures when using a RSA key-type issuer. Defaults to false.
+ ///
+ [JsonPropertyName("use_pss")]
+ public bool UsePss { get; set; }
+
+
+ public PKIRole()
+ {
+ // set defaults
+ KeyType = "rsa";
+ }
+ }
+}
diff --git a/src/VaultSharp/V1/SecretsEngines/PKI/PKIRoleNames.cs b/src/VaultSharp/V1/SecretsEngines/PKI/PKIRoleNames.cs
new file mode 100644
index 00000000..709f716d
--- /dev/null
+++ b/src/VaultSharp/V1/SecretsEngines/PKI/PKIRoleNames.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace VaultSharp.V1.SecretsEngines.PKI
+{
+ public class PKIRoleNames
+ ///
+ /// Vault Response Model containing list of role keys (role names)
+ ///
+ {
+ ///
+ /// Gets or sets list of certificate keys (serial numbers)
+ ///
+ ///
+ /// List of certificate keys (serial numbers)
+ ///
+ [JsonPropertyName("keys")]
+ public List Keys { get; set; }
+ }
+}
diff --git a/src/VaultSharp/V1/SecretsEngines/PKI/PKISecretsEngineProvider.cs b/src/VaultSharp/V1/SecretsEngines/PKI/PKISecretsEngineProvider.cs
index 2e89fca9..a0cfde22 100644
--- a/src/VaultSharp/V1/SecretsEngines/PKI/PKISecretsEngineProvider.cs
+++ b/src/VaultSharp/V1/SecretsEngines/PKI/PKISecretsEngineProvider.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using VaultSharp.Core;
@@ -26,7 +28,7 @@ public async Task> GetCredentialsAsync(string pki
return result;
}
-
+
public async Task> SignCertificateAsync(string pkiRoleName, SignCertificatesRequestOptions signCertificatesRequestOptions, string pkiBackendMountPoint = null, string wrapTimeToLive = null)
{
Checker.NotNull(pkiRoleName, "pkiRoleName");
@@ -77,7 +79,7 @@ public async Task ReadCACertificateAsync(CertificateFormat c
: CertificateFormat.der;
var result = await _polymath.MakeVaultApiRequest(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, "/ca" + format, HttpMethod.Get, rawResponse: true).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
-
+
return new RawCertificateData
{
CertificateContent = result,
@@ -89,7 +91,7 @@ public async Task> ReadCertificateAsync(string serial
{
Checker.NotNull(serialNumber, "serialNumber");
- var certificateDataSecret = await _polymath.MakeVaultApiRequest>(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, "/cert/" + serialNumber , HttpMethod.Get, unauthenticated: true).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
+ var certificateDataSecret = await _polymath.MakeVaultApiRequest>(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, "/cert/" + serialNumber, HttpMethod.Get, unauthenticated: true).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
return certificateDataSecret;
}
@@ -106,7 +108,7 @@ public async Task> ListRevokedCertificatesAsync(string p
return result;
}
-
+
public async Task> ReadDefaultIssuerCertificateChainAsync(CertificateFormat certificateFormat, string pkiBackendMountPoint = null)
{
if (certificateFormat != CertificateFormat.json
@@ -126,5 +128,54 @@ public async Task> ReadDefaultIssuerCertificateChainAsyn
var certificateDataSecret = await _polymath.MakeVaultApiRequest>(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, path, HttpMethod.Get, unauthenticated: true).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
return certificateDataSecret;
}
+
+ public async Task> GenerateRootAsync(string type, GenerateRootRequest generateRootRequest, string pkiBackendMountPoint = null)
+ {
+ var path = $"/root/generate/{type}";
+ var generatedRootDataSecret = await _polymath.MakeVaultApiRequest>(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, path, HttpMethod.Post, generateRootRequest).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
+ return generatedRootDataSecret;
+ }
+
+ public async Task> ListRolesAsync(string pkiBackendMountPoint = null)
+ {
+ var path = "/roles?list=true";
+ var roleNamesSecret = new Secret() { Data = new PKIRoleNames() { Keys = new List() } };
+
+ try
+ {
+ roleNamesSecret = await _polymath.MakeVaultApiRequest>(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, path, HttpMethod.Get).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
+ }
+ catch (VaultApiException ex)
+ {
+ // if the role list is empty, we will get a 404 response with { "errors": [] }
+ // make sure it's that error only, and return the empty list if so
+ if (ex.StatusCode != 404)
+ {
+ throw;
+ }
+ }
+ return roleNamesSecret;
+ }
+
+ public async Task> ReadRoleAsync(string pkiRoleName, string pkiBackendMountPoint = null)
+ {
+ var path = $"/roles/{pkiRoleName}";
+ var roleDetailsSecret = await _polymath.MakeVaultApiRequest>(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, path, HttpMethod.Get).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
+ return roleDetailsSecret;
+ }
+
+ public async Task> WriteRoleAsync(string pkiRoleName, PKIRole pkiRoleDetails, string pkiBackendMountPoint = null)
+ {
+ var path = $"/roles/{pkiRoleName}";
+ var roleDetailsSecret = await _polymath.MakeVaultApiRequest>(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, path, HttpMethod.Post, pkiRoleDetails).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
+ return roleDetailsSecret;
+ }
+
+ public async Task> PatchRoleAsync(string pkiRoleName, PKIRole pkiRoleDetails, string pkiBackendMountPoint = null)
+ {
+ var path = $"/roles/{pkiRoleName}";
+ var roleDetailsSecret = await _polymath.MakeVaultApiRequest>(pkiBackendMountPoint ?? _polymath.VaultClientSettings.SecretsEngineMountPoints.PKI, path, new HttpMethod("PATCH"), pkiRoleDetails).ConfigureAwait(_polymath.VaultClientSettings.ContinueAsyncTasksOnCapturedContext);
+ return roleDetailsSecret;
+ }
}
}
\ No newline at end of file
diff --git a/test/VaultSharp.Samples/Backends/Secrets/PKISecretsBackendSamples.cs b/test/VaultSharp.Samples/Backends/Secrets/PKISecretsBackendSamples.cs
new file mode 100644
index 00000000..2096ddac
--- /dev/null
+++ b/test/VaultSharp.Samples/Backends/Secrets/PKISecretsBackendSamples.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using VaultSharp.V1.SecretsEngines;
+using VaultSharp.V1.SecretsEngines.PKI;
+using Xunit;
+
+namespace VaultSharp.Samples
+{
+ partial class Program
+ {
+ private static void RunPKISecretsBackendSamples()
+ {
+ var pkiMountPath = Guid.NewGuid().ToString();
+
+ // mount a new v1 kv
+ var pkiSecretsEngine = new SecretsEngine
+ {
+ Type = SecretsEngineType.PKI,
+ Config = new Dictionary(),
+ Path = pkiMountPath
+ };
+
+ _authenticatedVaultClient.V1.System.MountSecretBackendAsync(pkiSecretsEngine).Wait();
+
+ var secretBackends = _authenticatedVaultClient.V1.System.GetSecretBackendsAsync().Result;
+ DisplayJson(secretBackends);
+ Assert.True(secretBackends.Data.Any());
+ var pkiPath = Guid.NewGuid().ToString();
+
+ // verify the role list is initially empty
+ var roleListResponse = _authenticatedVaultClient.V1.Secrets.PKI.ListRolesAsync(pkiMountPath).Result;
+
+ Assert.Empty(roleListResponse.Data?.Keys);
+
+ // write a new role
+ var allowedDomain = "test.com";
+ var roleName = "testRole";
+
+ var newRole = new PKIRole()
+ {
+ AllowedDomains = new List() { allowedDomain },
+ AllowSubdomains = true,
+ };
+
+ var newRoleResponse = _authenticatedVaultClient.V1.Secrets.PKI.WriteRoleAsync(roleName, newRole, pkiMountPath).Result;
+ Assert.True(newRoleResponse.Data.AllowedDomains.Exists(d => d == allowedDomain));
+
+ // verify it shows up in the list now
+ roleListResponse = _authenticatedVaultClient.V1.Secrets.PKI.ListRolesAsync(pkiMountPath).Result;
+
+ Assert.Single(roleListResponse.Data.Keys);
+
+ // generate a new root CA
+ var newRoot = new GenerateRootRequest()
+ {
+ CommonName = "test.com",
+ IssuerName = "testroot_test_com",
+ KeyName = "testroot_key",
+ };
+
+ var newRootResponse = _authenticatedVaultClient.V1.Secrets.PKI.GenerateRootAsync("exported", newRoot, pkiMountPath).Result;
+
+ Assert.NotEmpty(newRootResponse.Data.PrivateKey);
+
+ // issue a cert
+
+ var signCertOptions = new SignCertificatesRequestOptions()
+ {
+ CommonName = "my.test.com",
+ Csr = "-----BEGIN CERTIFICATE REQUEST-----\r\nMIIChDCCAWwCAQAwFjEUMBIGA1UEAwwLbXkudGVzdC5jb20wggEiMA0GCSqGSIb3\r\nDQEBAQUAA4IBDwAwggEKAoIBAQCQj/loI/Us7ayc/GOQlDWWv/lH+pcJ9g3w2Q/U\r\nzl8LBR7CD6Lve7TzBHxXU77gpg/lrCksr9LfE85FofhMy2WdEDTQw2BqdA/xwphh\r\nGsmaV+gfniZT96KzTOTRfMLE8Lf88bw5us7ha12MdJEhVX72kqXs7r/Hx5wz6gyw\r\niKcuezeTjp3r0qBPUIgHDgSg2TCVPs+THuhozQd4InQFU4HIWrDnR8unm5udRWId\r\nXOfxcQcgPp+UhzHzj+H/TfhqVeDzSvjVyir0llRAg7mNZctb/8lyOFPOTZkm4dWW\r\nzWVowYctnWgL05KcFK2wOJry16+gyzJaDE4EoxA/+YXVlnsfAgMBAAGgKTAnBgkq\r\nhkiG9w0BCQ4xGjAYMBYGA1UdEQQPMA2CC215LnRlc3QuY29tMA0GCSqGSIb3DQEB\r\nCwUAA4IBAQA41h0RFx8NLNSq92sgQwAh+FjZNqcjq8sLj7P0jwYi0CJrGf2fFfw1\r\nRhcZefwc3uGPsa/LXyGeBO4k88q8hA6x0B3yUhH26nC+OI1jhfj97x5pl+JFGkYT\r\nAP5Su9vwDKc22T8cv9K7Yzor3mfGc+Vs4HPH/pasg5cTqTageXNiBMd9VZKXzvrb\r\nhQyjW1uomsKCTlqCcjkRSA6eppxdAYCOKR/cfNNai2cIhZXuLZ4Y3gLosk/J8MGP\r\npRgz1SBls41Vk9gP8XF9e8eAKXVvnxSoCbifdNfjBkLaQgfaYpCV+NKBwQBmz7ad\r\nHjirOTU4elr/qUq91gyYuakOCZGWKm0k\r\n-----END CERTIFICATE REQUEST-----",
+ TimeToLive = "24h"
+ };
+
+ var newCertResponse = _authenticatedVaultClient.V1.Secrets.PKI.SignCertificateAsync(roleName, signCertOptions, pkiMountPath).Result;
+
+ Assert.NotEmpty(newCertResponse.Data.CertificateContent);
+
+ _authenticatedVaultClient.V1.System.UnmountSecretBackendAsync(pkiMountPath).Wait();
+ }
+ }
+}
diff --git a/test/VaultSharp.Samples/Backends/SecretsBackendSamples.cs b/test/VaultSharp.Samples/Backends/SecretsBackendSamples.cs
index 23e5684a..7f78e2c4 100644
--- a/test/VaultSharp.Samples/Backends/SecretsBackendSamples.cs
+++ b/test/VaultSharp.Samples/Backends/SecretsBackendSamples.cs
@@ -24,6 +24,9 @@ private static void RunSecretsEngineSamples()
Console.WriteLine("\n RunTransitSecretsBackendSamples \n");
RunTransitSecretsBackendSamples();
+
+ Console.WriteLine("\n RunPkiSecretsBackendSamples \n");
+ RunPKISecretsBackendSamples();
}
}
}
diff --git a/test/VaultSharp.Samples/Program.cs b/test/VaultSharp.Samples/Program.cs
index f8d01b80..eea5c034 100644
--- a/test/VaultSharp.Samples/Program.cs
+++ b/test/VaultSharp.Samples/Program.cs
@@ -8,7 +8,7 @@ namespace VaultSharp.Samples
{
partial class Program
{
- private const string ExpectedVaultVersion = "1.17.5";
+ private const string ExpectedVaultVersion = "1.17.6+ent";
private static IVaultClient _unauthenticatedVaultClient;
private static IVaultClient _authenticatedVaultClient;
@@ -103,7 +103,7 @@ private static VaultClientSettings GetVaultClientSettings(IAuthMethodInfo authMe
Console.WriteLine(_responseContent);
}
},
- Namespace = "bhjk",
+ //Namespace = "bhjk", // ??
MyHttpClientProviderFunc = handler => new HttpClient(handler)
};