grails - RestClientBuilder - rest

I am using the current version of rest client builder plugin. I tested out the uri via curl:
curl --user username:password https://localhost:8085/rest/api/latest/plan.json?os_authType=basic
I get the expected json in return. When I try to translate this to grails using the plugin like this:
RestBuilder rb = new RestBuilder()
def response = rb.get("https://localhost:8085/rest/api/latest/plan.json?os_authType=basic"){
auth 'username', 'password'
}
response.json instanceof JSONObject
I get this error:
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Why does it work in curl and not with the plugin? How do I get this to work?
Thanks!

You need to add the root certificate to the store of the trusted ones.
http://docs.oracle.com/javase/tutorial/security/toolsign/rstep2.html
Import the Certificate as a Trusted Certificate
Before you can grant the signed code permission to read a specified file, you need to import Susan's certificate as a trusted certificate in your keystore.
Suppose that you have received from Susan
the signed JAR file sCount.jar, which contains the Count.class file, and
the file Example.cer, which contains the public key certificate for the public key corresponding to the private key used to sign the JAR file.
Even though you created these files and they haven't actually been transported anywhere, you can simulate being someone other than the creater and sender, Susan. Pretend that you are now Ray. Acting as Ray, you will create a keystore named exampleraystore and will use it to import the certificate into an entry with an alias of susan.
A keystore is created whenever you use a keytool command specifying a keystore that doesn't yet exist. Thus we can create the exampleraystore and import the certificate via a single keytool command. Do the following in your command window.
Go to the directory containing the public key certificate file Example.cer. (You should actually already be there, since this lesson assumes that you stay in a single directory throughout.)
Type the following command on one line:
keytool -import -alias susan
-file Example.cer -keystore exampleraystore
Since the keystore doesn't yet exist, it will be created, and you will be prompted for a keystore password; type whatever password you want.
The keytool command will print out the certificate information and ask you to verify it, for example, by comparing the displayed certificate fingerprints with those obtained from another (trusted) source of information. (Each fingerprint is a relatively short number that uniquely and reliably identifies the certificate.) For example, in the real world you might call up Susan and ask her what the fingerprints should be. She can get the fingerprints of the Example.cer file she created by executing the command
keytool -printcert -file Example.cer
If the fingerprints she sees are the same as the ones reported to you by keytool, the certificate has not been modified in transit. In that case you let keytool proceed with placing a trusted certificate entry in the keystore. The entry contains the public key certificate data from the file Example.cer and is assigned the alias susan.

You can just disable SSL check for RestBuilder.
See an example of code:
static Scheme disableSSLCheck() {
def sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, [new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
#Override
X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0]
}
}] as TrustManager[], new SecureRandom())
def sf = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
def httpsScheme = new Scheme("https", sf, 443)
httpsScheme
}
And register this Scheme to the RestClient:
Scheme httpsScheme = disableSSLCheck()
restClient.client.connectionManager.schemeRegistry.register(httpsScheme)

Mb too late but have a look here.
https://gist.github.com/thomastaylor312/80fcb016020e4115aa64320b98fb0017
I do have it as separate method in my Integration test
def static disableSSLCheck() {
def nullTrustManager = [
checkClientTrusted: { chain, authType -> },
checkServerTrusted: { chain, authType -> },
getAcceptedIssuers: { null }
]
def nullHostnameVerifier = [
verify: { hostname, session -> true }
]
SSLContext sc = SSLContext.getInstance("SSL")
sc.init(null, [nullTrustManager as X509TrustManager] as TrustManager[], null)
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory())
HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier as HostnameVerifier)
}
And then just
void "test authentication"(){
given:
String url = "j_spring_security_check"
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>()
form.add("grant_type", "password")
form.add("j_username", "vadim#ondeviceresearch.com")
form.add("j_password", "notSecure")
form.add("_spring_security_remember_me", "true")
//TODO SET username and pass
//todo get token back
disableSSLCheck()
when:
RestResponse response = rest.post(host + url){
accept("application/json")
contentType("application/x-www-form-urlencoded")
body(form)
}
response
then:
response.status == 200
}

Related

Flutter Dio Networking: SSL pinning using certificate's public hashed key string (not a file)

I currently use the following snippet to include my SSL certificate file into the http client:
final List<int>? _certBytes; //I read it from .cer file included in the project
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
if (_certBytes != null) {
SecurityContext sc = SecurityContext();
sc.setTrustedCertificatesBytes(_certBytes!);
HttpClient httpClient = HttpClient(context: sc);
return httpClient;
} else {
client.badCertificateCallback =
((X509Certificate cert, String host, int port) => true);
return client;
}
};
while this code works well, it will stop working if the certificate is expired, which means that I need to add a new certificate file into the app and upload again to the app stores, so I decided to host the certificate as a hashed string in Firebase's Remote Config and read it upon app launch, so I can change the certificate remotely without building new versions for the app, but couldn't find a way to set the SecurityContext with a certificate hashed string in the Dio's HTTPClient

Akka. How to set a pem certificate in a https request

I'm using Akka (version 2.5.18) to send JSON strings to a specific server via https. I have used a poolRouter (balancing-pool with 10 instances) in order to create a pool of actors that are going to send JSONs (generated from different customers) to a single server:
val router: ActorRef = system.actorOf(
FromConfig.props(Props(new SenderActor(configuration.getString("https://server.com"), this.self))),
"poolRouter"
)
The project specification says that the requests can also be sent using curl:
curl -X PUT --cert certificate.pem --key private.key -H 'Content-Type: application / json' -H 'cache-control: no-cache' -d '[{"id" : "test"}] 'https://server.com'
Where "certificate.pem" is the tls certificate of the customer and "private.key" is the private key used to generate the CSR of the customer.
I'm using a balancing-pool because I will have a very big set of certificates (one for each customer) and I need to send the requests concurrently.
My approach is to have a "SenderActor" class that will be created by the balancing pool. Each actor, upon the reception of a message with a "customerId" and the JSON data generated by this customer, will send a https request:
override def receive: Receive = {
case Data(customerId, jsonData) =>
send(customerId(cid, jsonData))
Each SenderActor will read the certificate (and the private key) based on a path using the customerId. For instance, the customerId: "cust1" will have their certificate and key stored in "/home/test/cust1". This way, the same actor class can be used for all the customers.
According to the documentation, I need to create a HttpsConnectionContext in order to send the different requests:
def send(customerId: String, dataToSend): Future[HttpResponse] = {
// Create the request
val req = HttpRequest(
PUT,
uri = "https://server.com",
entity = HttpEntity(`application/x-www-form-urlencoded` withCharset `UTF-8`, dataToSend),
protocol = `HTTP/1.0`)
val ctx: SSLContext = SSLContext.getInstance("TLS")
val permissiveTrustManager: TrustManager = new X509TrustManager() {
override def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
override def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
override def getAcceptedIssuers(): Array[X509Certificate] = Array.empty
}
ctx.init(Array.empty, Array(permissiveTrustManager), new SecureRandom())
val httpsConnContext: HttpsConnectionContext = ConnectionContext.https(ctx)
// Send the request
Http(system).singleRequest(req, httpsConnContext)
}
The problem I have is that I don't have any clue about how to "set the certificate and the key" in the request, so that the server accepts them.
For instance, I can read the certificate using the following code:
import java.util.Base64
val certificate: String => String = (customer: String) => IO {
Source.fromInputStream(getClass.getClassLoader
.getResourceAsStream("/home/test/".concat(customer).concat("_cert.pem")))
.getLines().mkString
}.unsafeRunSync()
val decodedCertificate = Base64.getDecoder.decode(certificate(customerId)
.replaceAll(X509Factory.BEGIN_CERT, "").replaceAll(X509Factory.END_CERT, ""))
val cert: Certificate = CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(decodedCertificate))
But I don't know how to "set" this certificate and the private key in the request (which is protected by a passphrase), so that the server accepts it.
Any hint or help would be greatly appreciated.
The following allows making a https request and identifying yourself with a private key from a x.509 certificate.
The following libraries are used to manage ssl configuration and to make https calls:
ssl-config
akka-http
Convert your pem certificate to pks12 format as defined here
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt
Define key-store in your application.conf. It supports only pkcs12 and because of
this step 1 is required.
ssl-config {
keyManager {
stores = [
{
type = "pkcs12"
path = "/path/to/pkcs12/cetificate"
password = changeme //the password is set when using openssl
}
]
}
}
Load ssl config using special akka trait DefaultSSLContextCreation
import akka.actor.ActorSystem
import akka.actor.ExtendedActorSystem
import akka.http.scaladsl.DefaultSSLContextCreation
import com.typesafe.sslconfig.akka.AkkaSSLConfig
import com.typesafe.sslconfig.ssl.SSLConfigFactory
class TlsProvider(val actorSystem: ActorSystem) extends DefaultSSLContextCreation {
override protected def sslConfig: AkkaSSLConfig =
throw new RuntimeException("Unsupported behaviour when creating new sslConfig")
def httpsConnectionContext() = {
val akkaSslConfig =
new AkkaSSLConfig(system.asInstanceOf[ExtendedActorSystem], SSLConfigFactory.parse(system.settings.config))
createClientHttpsContext(akkaSslConfig)
}
}
Create a https context and use in http connection pool.
Http(actorSystem).cachedHostConnectionPoolHttps[RequestContext](
host = host,
port = portValue,
connectionContext = new TlsProvider(actorSystem).httpsConnectionContext()
)
Or set connection context to Http(actorSystem).singleRequest method.
In summary, I used ssl-config library to manage certificates instead of doing it programmatically yourself. By defining a keyManager in a ssl-config, any http request done with help of custom httpsConnectionContext will use the certificate to identify the caller/client.
I focused on describing how to establish a https connection using client certificate. Any dynamic behavior for managing multiple certificates is omitted. But I hope this code should be able give you understanding how to proceed.

Client Authentication with Azure KeyVault Secret: PFX -> CERT

PFX is uploaded to AzureKeyVault and it is fetched with GetSecretAsync azure call successfully.
Import the cert from pfx. now this certificate is used for client authentication.
Question:
In this case, the private key is not available in the machine then how does the client authentication work ?
for reference Code to Fetch the pfx and import to certificate:
secretBundle = await kvClientProvider.GetSecretAsync(secretUri, timeoutCancellation.Token).ConfigureAwait(false);
if (0 == string.CompareOrdinal(secretBundle.ContentType,
CertificateContentType.Pfx))
{
var exportedCertCollection = new X509Certificate2Collection();
exportedCertCollection.Import(Convert.FromBase64String(secretBundle.Value));
var cert = exportedCertCollection.Cast<X509Certificate2>().Single(sc => sc.HasPrivateKey);
return cert;
}

phpseclib $X509->setDomain on CSR?

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";

How to create a truststore.jks with one certificate?

(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);