I have created a Certificate with an inf-file and certreq with the following settings:
$InfFile = #"
[NewRequest]`r
FriendlyName = $FQDN
Subject = "CN=$FQDN,OU=$OrganizationalUnit,O=$Organisation,L=$Locality,S=$State,C=$CountryName,E=$Email"`r
KeySpec = 1
KeyLength = 2048
Exportable = TRUE`r
RequestType = PKCS10`r
[Extensions]
2.5.29.17 = "{text}"
_continue_ = "DNS=$FQDN&"
_continue_ = "DNS=$SERVERNAME"
"#
If I run this in a Script, the certificate will be created. But I'm unable to export the private key even though I set the value of "Exportable" to true. It seems no key is associated with the certificate as shown in this picture:
Certificate without key
Here a Certificate with associated Key for reference:
Certificate with Key
I'm not quite sure what I am doing wrong, any ideas?
Cheers
Buffalosoldier
Just found the answer to my own question. I had to add the following to the inf file: MachineKeySet = TRUE
Related
I need either the secret for HMAC algorithms, or the PEM encoded private key for RSA and ECDSA used to function JWT.sign()
now I get result from function getSecret() and writeStream to a pfx file,then I use openssl get a private key.
const result = await client.getSecret(vaultUri, secretName, secretVersion);
let writeStream = fs.createWriteStream("1.pfx");
writeStream.write(result.value, 'base64');
openssl pkcs12 -in 1.pfx -nocerts -nodes -out 1.key
openssl rsa -in 1.key -out 1_pri.key
How can i get a private key through the result on node server without openssl?
Any help is much appreciated.
If you want to exact private key from .pfx , try this :
const fs = require('fs');
const forge = require('node-forge');
const file = fs.readFileSync("<path of your .pfx file>");
const p12Der = forge.util.decode64(file.toString('base64'));
const pkcs12Asn1 = forge.asn1.fromDer(p12Der);
const pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, '<your .pfx password>');
const { key } = pkcs12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag })[
forge.pki.oids.pkcs8ShroudedKeyBag
][0];
const pemPrivate = forge.pki.privateKeyToPem(key);
console.log(pemPrivate);
Result :
I am stuck, I am trying to sign a Json Web Token for Docusign. https://developers.docusign.com/esign-rest-api/guides/authentication/oauth2-jsonwebtoken Docusign just provides a RSA private and public key hash. That's it. The JWT must signed using RS256.
I found a JWT module https://www.powershellgallery.com/packages/JWT/1.1.0 but that requires that I have the certificate installed. But all I have is the key hash.
Levering some other code I found I was able to create a JWT token , although with the wrong algorithm. https://www.reddit.com/r/PowerShell/comments/8bc3rb/generate_jwt_json_web_token_in_powershell/
I've been trying modify it to use the RSACryto provider by creating a new object but ive been unsuccessful.
I tried to create a new object and see if I can some how import the key so that I can sign the token. But I cant seem to be able to do that.
$rsa = New-Object -TypeName System.Security.Cryptography.RSACryptoServiceProvider
$keyhash = "-----BEGIN RSA PRIVATE KEY-----
XXXXXX
-----END RSA PRIVATE KEY-----"
$blob = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($keyhash))
I tried to use the ImportCspBlob method but it requires that the string is converted to bytes but I cant seem to do that either.
Im not sure if I am even approaching this int he correct fashion. Ive been getting errors of either
Exception calling "ImportCspBlob" with "1" argument(s): "Bad Version of provider.
or
Cannot convert value to type "System.Byte". Error: "Input string was not in a correct format."
EDIT:
I have a work around using Node.js, although id still like to see if it is possible to do what I am trying to do natively in Powershell . This work aroun might be useful for some as there does not seem to be many references for using Powershell and Docusign API.
I found a node.JS script here that creates a JWT Token using the RS256 algorithm. https://github.com/BlitzkriegSoftware/NodejwtRSA ,
I stripped out all the extra stuff so the output to the console is only the token, and added the relevant scope to the "payload data" and under the sign options updated the sub, aud, and iss. The my RSA Private key was stored locally on the system in a file.
nodejs script - My modified version below
'use strict';
const path = require('path');
const fs = require('fs');
// https://github.com/auth0/node-jsonwebtoken
var jwt = require('jsonwebtoken');
// Private Key (must read as utf8)
var privateKey = fs.readFileSync('./rsatest.pk','utf8');
// Sample claims payload with user defined fields (this can be anything, but briefer is better):
var payload = { };
// Populate with fields and data
payload.scope = "signature impersonation";
// Values for the rfc7519 fields
var iss = "XXXXX-XXXX-XXX-XXX-XXX";
var sub = "XXXXX-XXXX-XXX-XXX-XXX";
var aud = "account-d.docusign.com";
// Expiration timespan: https://github.com/auth0/node-jsonwebtoken#token-expiration-exp-claim
var exp = "1h";
// JWT Token Options, see: https://tools.ietf.org/html/rfc7519#section-4.1 for the meaning of these
// Notice the `algorithm: "RS256"` which goes with public/private keys
var signOptions = {
issuer : iss,
subject: sub,
audience: aud,
expiresIn: exp,
algorithm: "RS256"
};
var token = jwt.sign(payload, privateKey, signOptions);
console.log(token)
process.exitCode = 0;
I called it from Powershell and feed the access token back into my script so i can then get my access token and start making my API calls.
#get the JWT token
$token = & node C:\temp\nodejwt.js
# Generate Header for API calls.
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/x-www-form-urlencoded")
$body ="grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$token"
$authuri = "https://account-d.docusign.com/oauth/token"
#send the JWT and get the access token
$accesstoken = Invoke-RestMethod -Method post -Uri $authuri -Headers $headers -Body $body -Verbose
$getheaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$getheaders.Add("Content-Type", "application/json")
$getheaders.Add('Authorization','Bearer ' + $accesstoken.access_token)
$geturi = "https://account-d.docusign.com/oauth/userinfo"
#use the access token to make api calls
Invoke-RestMethod -Method get -Uri $geturi -Headers $getheaders
I also had the same problem and was stuck.I used Python to get JWT. Be sure to install PyJWT and cryptography library.
$ pip install PyJWT
$ pip install pyjwt[crypto]
import time
import jwt
from datetime import datetime, timedelta
private_key= b'-----BEGIN PRIVATE KEY-----
`-----END PRIVATE KEY-----\n'
encoded_jwt = jwt.encode({
"iss":"<service account e-mail>",
"scope":"",
"aud":"",
"exp":int(time.time())+3600,
"iat":int(time.time())
}, private_key, algorithm='RS256')
encoded_jwt
I'm attempting to create a self signed certificate in KeyVault using the "Self" issuer.
$policy = New-AzureKeyVaultCertificatePolicy -SubjectName "CN=$($certificateName)" -IssuerName "Self" -ValidityInMonths 12
$policy.Exportable = $true
Add-AzureKeyVaultCertificate -VaultName $vaultName -Name $certificateName -CertificatePolicy $policy
However, when getting the certificate back it doesn't appear to have a private key.
Creating certificates directly in KeyVault doesn't seem hugely covered online, after digging into the rest API documentation and source code for the powershell cmdlets, I'm stumped.
I'm hoping it's something simple I've missed, as I wish to avoid creating the certificate locally..
If you'd like to retrieve your certificate along with its private key, then you can export it to a PFX file (with an empty password) on your disk via:
$vaultName = "my-vault-name"
$certificateName = "my-cert-name"
$pfxPath = [Environment]::GetFolderPath("Desktop") + "\$certificateName.pfx"
$pfxSecret = Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName
$pfxUnprotectedBytes = [Convert]::FromBase64String($pfxSecret.SecretValueText)
[IO.File]::WriteAllBytes($pfxPath, $pfxUnprotectedBytes)
If you'd like to view just the private key itself in-memory without writing to disk, then try:
$vaultName = "my-vault-name"
$certificateName = "my-cert-name"
$pfxPath = [Environment]::GetFolderPath("Desktop") + "\$certificateName.pfx"
$pfxSecret = Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName
$pfxUnprotectedBytes = [Convert]::FromBase64String($pfxSecret.SecretValueText)
$pfx = New-Object Security.Cryptography.X509Certificates.X509Certificate2
$pfx.Import($pfxUnprotectedBytes, $null, [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$pfx.PrivateKey.ExportParameters($true)
which will show the private parameters in addition to the exponent and modulus.
If you'd like to protect the PFX file on disk with your own password (as per the "Retrieve pfx file & add password back" instructions in this blog post), then try:
$vaultName = "my-vault-name"
$certificateName = "my-cert-name"
$pfxPath = [Environment]::GetFolderPath("Desktop") + "\$certificateName.pfx"
$password = "my-password"
$pfxSecret = Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName
$pfxUnprotectedBytes = [Convert]::FromBase64String($pfxSecret.SecretValueText)
$pfx = New-Object Security.Cryptography.X509Certificates.X509Certificate2
$pfx.Import($pfxUnprotectedBytes, $null, [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$pfxProtectedBytes = $pfx.Export([Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $password)
[IO.File]::WriteAllBytes($pfxPath, $pfxProtectedBytes)
As mentioned in the REST API docs here and here, Azure Key Vault (AKV) represents a given X.509 certificate via three interrelated resources: an AKV-certificate, an AKV-key, and an AKV-secret. All three will share the same name and the same version - to verify this, examine the Id, KeyId, and SecretId properties in the response from Get-AzureKeyVaultCertificate.
Each of these 3 resources provide a different perspective for viewing a given X.509 cert:
The AKV-certificate provides the public key and cert metadata of the X.509 certificate. It contains the public key's modulus and exponent (n and e), as well as other cert metadata (thumbprint, expiry date, subject name, and so on). In PowerShell, you can obtain this via:
(Get-AzureKeyVaultCertificate -VaultName $vaultName -Name $certificateName).Certificate
The AKV-key provides the private key of the X.509 certificate. It can be useful for performing cryptographic operations such as signing if the corresponding certificate was marked as non-exportable. In PowerShell, you can only obtain the public portion of this private key via:
(Get-AzureKeyVaultKey -VaultName $vaultName -Name $certificateName).Key
The AKV-secret provides a way to export the full X.509 certificate, including its private key (if its policy allows for private key exporting). As demonstrated above, the current base64-encoded certificate can be obtained in PowerShell via:
(Get-AzureKeyVaultSecret -VaultName $vaultName -Name $certificateName).SecretValueText
Following is C# code to retrieve all versions of a certificate, including their private keys, from newest to oldest, given its certificate name and KeyVault connection info. It uses the new Azure.Core, Azure.Identity, and Azure.Security.KeyVault.[Certificates|Secrets] SDK packages.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Secrets;
public static class CertTools
{
public static void MyMethod(string tenantId, string clientId, string clientSecret, Uri keyVaultUri)
{
var cred = new ClientSecretCredential(tenantId, clientId, clientSecret); // or any other means of obtaining Azure credential
var certs = GetAllCertificateVersions(keyVaultUri, cred, "MyCert");
}
public static List<X509Certificate2> GetAllCertificateVersions(Uri keyVaultUri, TokenCredential credential,
string certificateName)
{
var certClient = new CertificateClient(keyVaultUri, credential);
var secretClient = new SecretClient(keyVaultUri, credential);
var now = DateTimeOffset.UtcNow;
var certs = new List<X509Certificate2>();
foreach (var cert in certClient.GetPropertiesOfCertificateVersions(certificateName)
.OrderByDescending(x => x.CreatedOn)
// fetch all enabled, non-expired certificates. adjust this predicate if desired.
.Where(x => x.ExpiresOn >= now && (x.Enabled ?? false)))
{
var secret = secretClient.GetSecret(certificateName, cert.Version).Value;
certs.Add(new X509Certificate2(Convert.FromBase64String(secret.Value)));
}
return certs;
}
}
Thanks to #Nandun's answer here for pointing me in the right direction of using the SecretClient instead of CertificateClient, but that post was marked as a duplicate so posting this extended code here.
Is it possible to set x509 v3 extended attribute for subjectAltName on a CSR?
I am able to successfully generate a CSR, and then pass that to a CA to sign.
The CA is able to call functions like X509->setDomain("bob.com","*.bob.com","asdf.org"); and they appear in the final cert without issue (note: i am doing the sign, reload, set extensions, resign workaround for phpseclib).
The CSR process calling the same functions $X509->setExtension("id-ce-subjectAltName",array("names","here") ) or $X509->setDomain("domain1","domain2"); does not appear to set some X509 extended attributes in the CSR itself (openssl can), I have only been able to add SAN by the CA after re-import before the second signing.
Thanks!
Edit: I created an example CSR (sign, reload, x509v3 extensions, resign workaround) and it seems possible to set SOME extended attributes (CA: false, key usage, etc.) but NOT the subjectAltName info:
-----BEGIN CERTIFICATE REQUEST-----
MIIC1TCCAb8CAQAwVDELMAkGA1UEBgwCU1MxEDAOBgNVBAgMB09ic2N1cmUxDzAN
BgNVBAcMBlNlY3VyZTEQMA4GA1UECgwHYm9iY29ycDEQMA4GA1UEAwwHYm9iLmNv
bTCCASAwCwYJKoZIhvcNAQEBA4IBDwAwggEKAoIBAQCunNrjxEOILsESZ1osUkT3
zSeAHlzNiCBQnc/Xf+oW7Ir7wKfbHkV10cM583mw3Zy8rlCT/lUq0H+f3Uoc5/FA
dsYWhatlJRlTjv+yjSsxyB9i/hZ/KliP3Ix2O+Pq1wZIWfvk40hmCHHSB6YDtqt0
vXqUTIhH3SfyLtK9nd/6WG8bgIq9jgL6xvF3h44ynEwkuOnHt4a9WrflGX4KKcwo
OM98M/TAntDgSXBEYoLxenIbl3ypa7gtghVHHls3QOSay5QM87K3PJ3kSWVlZ3tN
tZGfbFdLiS+3MH8G3ujSX8XYeBnUj5jXi4SzpzQ9o5pyArL8DD8kC/Q9P/el1aZb
AgMBAAGgQDA+BgkqhkiG9w0BCQ4xMTAvMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/
BAwwCgYIKwYBBQUHAwEwBwYDVR0RBAAwCwYJKoZIhvcNAQELA4IBAQAUIhgLf6LH
WwhncU+yrNy6yHPHjFbipehUaS/Wa6FBbANpLyEdslqNwFD2FyXofSQVCB0L6VnH
NNXHcfZqWOT8+Xd0yNmfr/w+vg+s1yY4S0VAG6mxGsqwdIJqgXm2jaw0rMpzRs/k
wfOpJ+gyRTrQz9VJrn4xC4Uv6zTGNj56qZehDievW74SGISbGzj7AGmCxpp5/qZD
LN4Ls3wZ7I4TfuYZDh7qWuFwEAEEv40JPF2qO3VitvyAYrKg3bnUlFzQrOsfs4Ot
xzihLwkFEobih1bOEhzTnSv+lMckfw0DQ5Eb8mtFTC+/KOqEMMU5Hq1fm3B9Bkgs
FmqQZk4QlerY
-----END CERTIFICATE REQUEST-----
I figured it out, the SignCSR function in phpseclib doesnt respsect the setDomain function, or any content in the $this->domains array however it IS possible to manually encode and force set the subject alt names yourself:
$OPTIONS["altnames"] = array(
"bob.com",
"10.123.123.123",
"*.bob.com",
"asdf.fdsa",
);
$ALTNAMES = $this->altnames($OPTIONS["altnames"]);
if ( count($ALTNAMES) )
{
$CSR->setExtension("id-ce-subjectAltName" , $ALTNAMES );
}
protected function altnames($ALTNAMES)
{
//Sort names and IPs into two different arrays
$DOMAINS = array(); $IPS = array();
foreach ($ALTNAMES as $ALTNAME)
{
if ( filter_var($ALTNAME, FILTER_VALIDATE_IP) )
{
array_push($IPS,$ALTNAME);
}else{
array_push($DOMAINS,$ALTNAME);
}
}
// Create our altname array for the subjectAltName parser
$RETURN = array();
foreach ($DOMAINS as $DOMAIN) { array_push($RETURN, array("dNSName" => $DOMAIN ) ); }
foreach ($IPS as $IP) { array_push($RETURN, array("iPAddress" => $IP ) ); }
return $RETURN;
}
Now, the one caveat I found (that is a feature issue for phpseclib to consider) is the lack of IPv6 support. If you provide a valid IPv6 address in the altnames array (2620:153::1234) it will translate to IP Address: 0.0.0.0 because of the use of IP2LONG and LONG2IP embedded in the X509 code.
While not a showstopper for my use case, it is a minor annoyance and I have experimented with using a custom IP2BIN and BIN2IP function replacing the encode and decode logic for an IP in the library and not met with much success.
Example CSR generated with my code above:
-----BEGIN CERTIFICATE REQUEST-----
MIIC/DCCAeYCAQAwVDELMAkGA1UEBgwCU1MxEDAOBgNVBAgMB09ic2N1cmUxDzAN
BgNVBAcMBlNlY3VyZTEQMA4GA1UECgwHYm9iY29ycDEQMA4GA1UEAwwHYm9iLmNv
bTCCASAwCwYJKoZIhvcNAQEBA4IBDwAwggEKAoIBAQDOGLdjEr2IyW27e08hmRN+
Bcu4uOWAWEvxvY3+5pHdBQQEXhww5YQuEvpmgbKtav0j7aqFPDYNSXTv+aQNe9fq
cP3nZmKAWU+qbQMjWxwV9mEJOlWI214v7C8lLbMvBlny141J7KTvv1TGGLCBH/V2
EnQSdJzGDwXmJ2k0iChlQ7zl1TlonamYX9gefzp3N/DDp6kNhuPSX9zRorYIp5CC
WEIRmDdegwxHACrNu0K4xwuPjTRJf0oUkRsfBuDxqvBalQ0bzd/23fiQ51MEVla3
fUWL/+b2SKOlvgfu0XbfZ+Qx21DeyRQpqIWnv0gR4AM8qltxUgRjZloUfK5IQ8rp
AgMBAAGgZzBlBgkqhkiG9w0BCQ4xWDBWMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/
BAwwCgYIKwYBBQUHAwEwLgYDVR0RBCcwJYIHYm9iLmNvbYIJKi5ib2IuY29tgglh
c2RmLmZkc2GHBAp7e3swCwYJKoZIhvcNAQELA4IBAQAf1Vlt09ZBhVpxlmi+n93u
Gm8fNrd5afeIzWj0h4dFGJSOg5T8SkfL5txk2C9tQEdayQWB1kllx5rIqXAPe6gz
kbcjJn8l2IB3khIoKYmylmtV8Yo0Fl/Xba1oLCAsixbK+UxiSLgXqMryz9DBy40s
5oYXpy5JOnqL7BRC7b+Lk/chw7CcncPZI4rei6HM8WATymTySdrPoQegvBj0VIar
qHBZrMV9lsjTREJ9hvA/FycA/PNlP9y8N+eTF9SBrnTi8ix+v+Iirc43xeD2EVLG
6uqXecClji6OEOKcdDsH0D0HD1PMFmKB0FWvq71dt7eVIHkTPwTLFG2XAjfn6Fb8
-----END CERTIFICATE REQUEST-----
Decodes to:
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=SS, ST=Obscure, L=Secure, O=bobcorp, CN=bob.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ce:18:b7:63:12:bd:88:c9:6d:bb:7b:4f:21:99:
13:7e:05:cb:b8:b8:e5:80:58:4b:f1:bd:8d:fe:e6:
91:dd:05:04:04:5e:1c:30:e5:84:2e:12:fa:66:81:
b2:ad:6a:fd:23:ed:aa:85:3c:36:0d:49:74:ef:f9:
a4:0d:7b:d7:ea:70:fd:e7:66:62:80:59:4f:aa:6d:
03:23:5b:1c:15:f6:61:09:3a:55:88:db:5e:2f:ec:
2f:25:2d:b3:2f:06:59:f2:d7:8d:49:ec:a4:ef:bf:
54:c6:18:b0:81:1f:f5:76:12:74:12:74:9c:c6:0f:
05:e6:27:69:34:88:28:65:43:bc:e5:d5:39:68:9d:
a9:98:5f:d8:1e:7f:3a:77:37:f0:c3:a7:a9:0d:86:
e3:d2:5f:dc:d1:a2:b6:08:a7:90:82:58:42:11:98:
37:5e:83:0c:47:00:2a:cd:bb:42:b8:c7:0b:8f:8d:
34:49:7f:4a:14:91:1b:1f:06:e0:f1:aa:f0:5a:95:
0d:1b:cd:df:f6:dd:f8:90:e7:53:04:56:56:b7:7d:
45:8b:ff:e6:f6:48:a3:a5:be:07:ee:d1:76:df:67:
e4:31:db:50:de:c9:14:29:a8:85:a7:bf:48:11:e0:
03:3c:aa:5b:71:52:04:63:66:5a:14:7c:ae:48:43:
ca:e9
Exponent: 65537 (0x10001)
Attributes:
Requested Extensions:
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Extended Key Usage: critical
TLS Web Server Authentication
X509v3 Subject Alternative Name:
DNS:bob.com, DNS:*.bob.com, DNS:asdf.fdsa, IP Address:10.123.123.123
Signature Algorithm: sha256WithRSAEncryption
1f:d5:59:6d:d3:d6:41:85:5a:71:96:68:be:9f:dd:ee:1a:6f:
1f:36:b7:79:69:f7:88:cd:68:f4:87:87:45:18:94:8e:83:94:
fc:4a:47:cb:e6:dc:64:d8:2f:6d:40:47:5a:c9:05:81:d6:49:
65:c7:9a:c8:a9:70:0f:7b:a8:33:91:b7:23:26:7f:25:d8:80:
77:92:12:28:29:89:b2:96:6b:55:f1:8a:34:16:5f:d7:6d:ad:
68:2c:20:2c:8b:16:ca:f9:4c:62:48:b8:17:a8:ca:f2:cf:d0:
c1:cb:8d:2c:e6:86:17:a7:2e:49:3a:7a:8b:ec:14:42:ed:bf:
8b:93:f7:21:c3:b0:9c:9d:c3:d9:23:8a:de:8b:a1:cc:f1:60:
13:ca:64:f2:49:da:cf:a1:07:a0:bc:18:f4:54:86:ab:a8:70:
59:ac:c5:7d:96:c8:d3:44:42:7d:86:f0:3f:17:27:00:fc:f3:
65:3f:dc:bc:37:e7:93:17:d4:81:ae:74:e2:f2:2c:7e:bf:e2:
22:ad:ce:37:c5:e0:f6:11:52:c6:ea:ea:97:79:c0:a5:8e:2e:
8e:10:e2:9c:74:3b:07:d0:3d:07:0f:53:cc:16:62:81:d0:55:
af:ab:bd:5d:b7:b7:95:20:79:13:3f:04:cb:14:6d:97:02:37:
e7:e8:56:fc
OK, I think i found a "feature" in phpseclib's X509 signing function:
function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
If you sign the CSR, the code in the sign function claims to copy the X509 v3 extended attributes, however by the time it executes this line:
$csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
The result is always blank (an empty array), the attributes in $THIS (new) cert were overwritten somewhere in the previous ~30 lines of code.
I moved the csrexts line to the top of the Sign function, and attributes are now being copied correctly into generated certificates with X509 extended attributes in the CSR (that had to be manually calculated and set by my previous answer)
I will use sourceforge to try and post a bug request to save others the struggle hopefully.
OK, so I've got this to work using an example from the PHPSecLib documentation and playing around with it for a good few hours. Perhaps this is something added post February, but in any case, here is how to generate a CSR with an Subject Alternative Name:
$privKey = new Crypt_RSA();
extract($privKey->createKey());
$privKey->loadKey($privatekey);
$x509 = new File_X509();
$x509->setPrivateKey($privKey);
$x509->setDNProp('id-at-organizationName', 'Company');
$x509->setDNProp('id-at-organizationalUnitName', 'CompanyDepartment');
$x509->setDNProp('id-at-commonName', 'myurl.com');
$x509->setDNProp('id-at-localityName', $this->application);
$x509->loadCSR($x509->saveCSR($x509->signCSR()));
// Set extension request.
$x509->setExtension("id-ce-subjectAltName", [
['dNSName' => 'myalternativename.com', 'iPAddress' => 127.0.0.1]
]);
echo $x509->saveCSR($x509->signCSR());
That should print out the CSR with myalternativename.com as the SAN.
The bug still actual in 2020.
Cause of this bug is need for a valid $x509->currentCert array. The setExtension() function has no effect with empty currentCert array.
The currentCert array structure is equal to result of signCSR() function. Therefore you can just initialize it as following: $x509->currentCert = $x509->signCSR();
Now you can call setExtension() function.
Here is full example how to generate CSR with SAN attribute:
use \phpseclib\Crypt\RSA;
use \phpseclib\File\X509;
$privateKey = new RSA();
$privateKey->loadKey(file_get_contents(__DIR__ . '/private.key'));
$x509 = new X509();
$x509->setPrivateKey($privateKey);
$x509->setDNProp('commonname', 'example.com');
$x509->setDNProp('emailaddress', 'admin#mail.com');
$x509->currentCert = $x509->signCSR(); // Important!
$x509->setExtension('id-ce-subjectAltName', [
['iPAddress' => '127.0.0.1'],
['dNSName' => 'www.example.com'],
]);
$csr = $x509->signCSR();
file_put_contents(__DIR__ . '/domain.csr', $x509->saveCSR($csr));
echo "OK\n";
(I'm a newby in cryptographic things.)
I have an setup program written in C#. This asks the user to input the server URL. Then it connects to this server and stores this server certificate into a truststore file that is used by the installed Java REST service.
The truststore file is created by keytool.exe:
keytool.exe -alias anAlias -import -file cert.cer -noprompt -keystore truststore.jks -storepass aPassword
Now we don't want to use keytool.exe. We want to create the keystore by C#. My first tries are as follows:
class AddCertToTruststore
{
public static void Do()
{
ServicePointManager.ServerCertificateValidationCallback += Validate;
X509Certificate2 cert = new X509Certificate2("cert.cer");
cert.Archived = true;
bool ok = cert.Verify(); // always false
X509Certificate2Collection certs = new X509Certificate2Collection();
certs.Add(cert);
byte[] bytes = certs.Export(X509ContentType.Pkcs12);
File.WriteAllBytes("truststore.jks", bytes);
ServicePointManager.ServerCertificateValidationCallback -= Validate;
}
private static bool Validate(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
return true;
}
}
This code creates a truststore but I'm missing the certificate in it. If I open the truststore.jks with KeyStore Explorer 5.1 there is not any certificate in it. What am I doing wrong?
The certificate is a self-signed certificate. cert.Verify() returns always false.
It's just one line that is missing:
cert.FriendlyName = "anAlias";
It works also without the validation handler and without setting Archived property. So the shortest code is:
X509Certificate2 cert = new X509Certificate2(#"cert.cer");
cert.FriendlyName = "anAlias";
X509Certificate2Collection certs = new X509Certificate2Collection();
certs.Add(cert);
byte[] bytes = certs.Export(X509ContentType.Pkcs12);
File.WriteAllBytes(#"truststore.jks", bytes);