I'm trying to connect to a database via SSL within Google Apps Script, referencing these docs. The error is:
Execution failed: Failed to establish a database connection. Check connection string, username and password.
I can use these exact same parameters from another db client (Sequel Pro) and it works fine. I've got the db accepting connections from any IP address (0.0.0.0/0).
I believe I've eliminated all the other variables (user name, password, etc. etc.), its only when I attempt to use SSL within Apps Script that it fails.
Can anyone provide a working example of connecting to a MySQL database with SSL within Google Apps Script?
My apps script:
function connectDb() {
var address = 'x.x.x.x'; // no, I'm not literally using x's in my ip address
var instanceUrl = 'jdbc:mysql://' + address + ':3306/';
var clientSslKey = '-----BEGIN RSA PRIVATE KEY-----\n' +
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n' +
/* snip */
'-----END RSA PRIVATE KEY-----';
var clientSslCertificate = '-----BEGIN CERTIFICATE-----\n' +
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n' +
/* snip */
'-----END CERTIFICATE-----';
var serverSslCertificate = '-----BEGIN CERTIFICATE-----\n' +
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n' +
/* snip */
'-----END CERTIFICATE-----';
// https://developers.google.com/apps-script/reference/jdbc/jdbc#getconnectionurl-info
var connParams = {
user: 'temp',
// no password set
_serverSslCertificate: serverSslCertificate,
_clientSslCertificate: clientSslCertificate,
_clientSslKey: clientSslKey,
};
var conn = Jdbc.getConnection(instanceUrl, connParams);
}
EDIT
I filed a bug here which got marked as a duplicate of this other one which as a "P2" priority. Maybe that means it will be fixed soonish?
I can confirm that I can connect to a MySQL database with SSL within Google Apps Script.
It's important to note useSSL=true is indeed necessary
I was able to get it working by following the example at https://issuetracker.google.com/issues/36761592#comment18 (relevant snippet repeated below):
var conn = Jdbc.getConnection('jdbc:mysql://<ip address>/<db name>?useSSL=true', {
user: '<user>',
password: '<pass>',
_serverSslCertificate: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
_clientSslCertificate: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
_clientSslKey: '-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----'
});
If you're using a Google Cloud SQL instance, you may want to consider using getCloudSqlConnection(). Presumably it's encrypted as well (based on the fact that I can indeed get a connection when the corresponding instance has "Only secured connections are allowed to connect to this instance." enabled), and, importantly, doesn't require you to manage (i.e. not lose control of) the key and certs yourself.
Related
I was tasked to create a integration service between our SharePoint app and one service provider. One requirement of the service provider I'm going to integrate with is to provide them a public key which they will use to verify my request which was signed using our own private key.
Initially I created a console app which reads the certificate store and gets the private key which to use to sign my request and all. The console app works fine so I decided to move it now within our SharePoint application. Unfortunately it fails in this specific part of the code:
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
The whole code snippet which gets the certificate and does the signing can be found below:
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.MaxAllowed);
var certs = store.Certificates.Find(X509FindType.FindByThumbprint, "thumbprinthere", true);
if (certs.Count > 0)
{
privateCert = certs[0];
}
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
byte[] sig = key.SignData(Encoding.ASCII.GetBytes(data), CryptoConfig.MapNameToOID("SHA256"));
string signature = Convert.ToBase64String(sig);
[UPDATE]
I tried following the steps in this link. I first uninstalled my existing private key in the server. I then imported it back to the Certificate store and confirmed that there was a Thumbprint property. After that, I ran findprivatekey.exe and was able to navigate to the MachineKeys folder. From there I added different users ranging from Network Services, IIS_IUSRS and even local accounts I used to login to the server as well as SPFarm admin but I still keep getting the error.
I also made sure that the key I added was exportable so there should be a way for it the application to extract the private key attached to the certificate.
[UPDATE 2]
I updated the code so that it just returns one certificate prior to assigning it to the variable I was using to extract the private key. Still the same issue even if I can see that the certs variable is returning exactly one record.
After much checking I realized I missed one important part in calling the method code block above. I forgot to wrap it an elevate privilege block. After doing that, the code functioned similarly as my console app.
SPSecurity.RunWithElevatedPrivileges(delegate())
{
...
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.MaxAllowed);
var certs = store.Certificates.Find(X509FindType.FindByThumbprint, "<thumbprinthere>", true);
if (certs.Count > 0)
{
privateCert = certs[0];
}
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
byte[] sig = key.SignData(Encoding.ASCII.GetBytes(data), CryptoConfig.MapNameToOID("SHA256"));
string signature = Convert.ToBase64String(sig);
...
});
I have a security question about RijndaelManaged and
ServicePointManager.
I have implemented a system where C# application is encrypting data, such as user credentials and some XML data. Then I use WebClient to send encrypted user credentials with some encrypted XML document containing instructions - to my Tomcat Java Web application. The job of the Java Application: is to decrypt user credentials and XML instructions – perform instructions and respond back to C# with an encrypted XML result.
All connections from my C# application to Tomcat server are with SSL enabled (Self signed certificate for now).
First Question: Given the fact that my C# application by default always connecting to my Server (only) with SSL enabled. Can I simply implement the call back function as:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
As I understand that the call back function is used to Identify and validate certificate used by the Server I’m connecting to. If I were to give that application to – say one of my clients to connect to my Server (with SSL enabled) – is the code above satisfactory? If client uses my application to connect to another server that is not known and I have no Idea about its SSL certificate status – the code above should be replaced with an actual certificate validation function. Does my question make sense?
Second Question: I have encryption/decryption implemented using RijndaelManaged in my C# application. But the key I’m using is part of the C# application – the application is obfuscated. As I understand this is not a secure way.
Is there a reliable way for the C# application to receive the encryption/decryption key from my Web application. Or is there a way for the key to be generated in C# application that can be used by Web application to decrypt the data – if so: how do I generate that key and most important how do I send it to the server in a reliable secure way. Since the connection is SSL – can the key simply be a part of the encrypted stream?
Here is code that I’m using for encryption in my C# app.
private const string KEY = "samplekey";
private const int KEY_SIZE = 128;
private const int KEY_BITS = 16;
private string Encrypt(string textToEncrypt)
{
RijndaelManaged rijndaelCipher = new RijndaelManaged();
rijndaelCipher.Mode = CipherMode.CBC;
rijndaelCipher.Padding = PaddingMode.PKCS7;
rijndaelCipher.KeySize = KEY_SIZE;
rijndaelCipher.BlockSize = KEY_SIZE;
byte[] pwdBytes = Encoding.UTF8.GetBytes(KEY);
byte[] keyBytes = new byte[KEY_BITS];
int len = pwdBytes.Length;
if (len > keyBytes.Length)
{
len = keyBytes.Length;
}
Array.Copy(pwdBytes, 0, keyBytes, 0, len);
rijndaelCipher.Key = keyBytes;
rijndaelCipher.IV = keyBytes;
ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
byte[] plainText = Encoding.UTF8.GetBytes(textToEncrypt);
return System.Convert.ToBase64String(transform.TransformFinalBlock(plainText, 0, plainText.Length));
}
I have an application replicating data from a directory service using typical System.DirectoryServices.DirectoryEntry code. I now have a requirement to replicate from Novell eDirectory using SSL with a self-signed certificate. I suspect that the existing code would work with a valid certificate that could be verified, or perhaps if the self-signed cert is added to the local machine keystore. In order to make it work for sure with a self-signed cert however, the only solution I can find is to use the System.DirectoryServices.Protocols namespace and the LdapConnection class, whereby I can wire up a VerifyServerCertificate callback. I can't find any way of applying the same concept to a DirectoryEntry instance, or of connecting with an LdapConnection instance and somehow "converting" that to a DirectoryEntry instance. Maybe it isn't possible, I'd just like to confirm that really. Any other thoughts welcome.
The only pertinent link I've found is at: http://www.codeproject.com/Articles/19097/eDirectory-Authentication-using-LdapConnection-and
This is a phenomenal question.
I've been battling this same issue for a few days now, and I've finally got some definitive proof on why the DirectoryEntry object will not work in this scenario.
This particular Ldap server (running on LDAPS 636) also issues it's own self signed certificate. Using LdapConnection (and monitoring the traffic via Wireshark), I noticed a handshake taking place that does not occur when using DirectoryEntry :
The first sequence is the from the secured ldap server, the second sequence is from my machine. The code that prompts the second sequence is :
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
There are others way to "fake out" the callback, but this what I've been using.
Unfortunately, DirectoryEntry does not have an option or method to verify a self signed cert, thus the acceptance of the certificate never happens (second sequence), and the connection fails to initialize.
The only feasible way to accomplish this is by using LdapConnection, in conjunction with a SearchRequest and SearchResponse. This is what I've got so far :
LdapConnection ldapConnection = new LdapConnection("xxx.xxx.xxx:636");
var networkCredential = new NetworkCredential("Hey", "There", "Guy");
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
ldapConnection.AuthType = AuthType.Negotiate;
ldapConnection.Bind(networkCredential);
SearchRequest request = new SearchRequest("DC=xxx,DC=xxx,DC=xxx", "(sAMAccountName=3074861)", SearchScope.Subtree);
SearchResponse response = (SearchResponse)ldapConnection.SendRequest(request);
if(response.Entries.Count == 1)
{SearchResultEntry entry = response.Entries[0];
string DN = entry.DistinguishedName;}
From there you can gather AD Properties from the SearchResponse, and process accordingly. This is a total bummer though, because the SearchRequest seems to be much slower then using the DirectoryEntry.
Hope this helps!
I promise, this will be my last post on this particular question. :)
After another week of research and development, I have a nice solution to this, and it has worked exceedingly well for me thus far.
The approach is somewhat different then my first answer, but in general, it's the same concept; using the LdapConnection to force validation of the certificate.
//I set my Domain, Filter, and Root-AutoDiscovery variables from the config file
string Domain = config.LdapAuth.LdapDomain;
string Filter = config.LdapAuth.LdapFilter;
bool AutoRootDiscovery = Convert.ToBoolean(config.LdapAuth.LdapAutoRootDiscovery);
//I start off by defining a string array for the attributes I want
//to retrieve for the user, this is also defined in a config file.
string[] AttributeList = config.LdapAuth.LdapPropertyList.Split('|');
//Delcare your Network Credential with Username, Password, and the Domain
var credentials = new NetworkCredential(Username, Password, Domain);
//Here I create my directory identifier and connection, since I'm working
//with a host address, I set the 3rd parameter (IsFQDNS) to false
var ldapidentifier = new LdapDirectoryIdentifier(ServerName, Port, false, false);
var ldapconn = new LdapConnection(ldapidentifier, credentials);
//This is still very important if the server has a self signed cert, a certificate
//that has an invalid cert path, or hasn't been issued by a root certificate authority.
ldapconn.SessionOptions.VerifyServerCertificate += delegate { return true; };
//I use a boolean to toggle weather or not I want to automatically find and query the absolute root.
//If not, I'll just use the Domain value we already have from the config.
if (AutoRootDiscovery)
{
var getRootRequest = new SearchRequest(string.Empty, "objectClass=*", SearchScope.Base, "rootDomainNamingContext");
var rootResponse = (SearchResponse)ldapconn.SendRequest(getRootRequest);
Domain = rootResponse.Entries[0].Attributes["rootDomainNamingContext"][0].ToString();
}
//This is the filter I've been using : (&(objectCategory=person)(objectClass=user)(&(sAMAccountName={{UserName}})))
string ldapFilter = Filter.Replace("{{UserName}}", UserName);
//Now we can start building our search request
var getUserRequest = new SearchRequest(Domain, ldapFilter, SearchScope.Subtree, AttributeList);
//I only want one entry, so I set the size limit to one
getUserRequest.SizeLimit = 1;
//This is absolutely crucial in getting the request speed we need (milliseconds), as
//setting the DomainScope will suppress any refferal creation from happening during the search
SearchOptionsControl SearchControl = new SearchOptionsControl(SearchOption.DomainScope);
getUserRequest.Controls.Add(SearchControl);
//This happens incredibly fast, even with massive Active Directory structures
var userResponse = (SearchResponse)ldapconn.SendRequest(getUserRequest);
//Now, I have an object that operates very similarly to DirectoryEntry, mission accomplished
SearchResultEntry ResultEntry = userResponse.Entries[0];
The other thing I wanted to note here is that SearchResultEntry will return user "attributes" instead of "properties".
Attributes are returned as byte arrays, so you have to encode those in order to get the string representation. Thankfully, System.Text.Encoding contains a native ASCIIEncoding class that can handle this very easily.
string PropValue = ASCIIEncoding.ASCII.GetString(PropertyValueByteArray);
And that's about it! Very happy to finally have this figured out.
Cheers!
I have used below code to connect with ldaps using DirectoryEntry.
What i understood in my scenerio is directoryEntry does not work when ldaps is specified in server path or authentication type is mentioned as "AuthenticationTypes.SecureSocketsLayer" but if only ldaps port is mentioned at the end of server name it work. After having a look at wireshark log i can see handshake taking place as mentioned in above post.
Handshake:
Code:
public static SearchResultCollection GetADUsers()
{
try
{
List<Users> lstADUsers = new List<Users>();
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://adserver.local:636", "username", "password");
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.PropertiesToLoad.Add("samaccountname");
SearchResult result;
SearchResultCollection resultCol = search.FindAll();
Console.WriteLine("Record count " + resultCol.Count);
return resultCol;
}
catch (Exception ex)
{
Console.WriteLine("exception" + ex.Message);
return null;
}
}
I'm trying to create a user provider so that I can authenticate through an Active directory server.
The problem is that, unlike most other LDAP servers, Active directory doesn't allow to retrieve some user's password attribute, even encrypted.
Here is my User class :
class LdapUser implements UserInterface
{
private $username;
private $first_name;
private $last_name;
private $password;
private $salt;
private $roles;
public function __construct($username, $first_name, $last_name, $password, $salt, array $roles) {
$this->username = $username;
$this->first_name = $first_name;
$this->last_name = $last_name;
$this->password = $password;
$this->salt = $salt;
$this->roles = $roles;
}
...
}
And here is my loadUserByUsername method (In my UserProvider class) :
public function loadUserByUsername($username)
{
$server = "my_ldap_server";
$root_dn = "my_root_dn";
$root_pw = "my_root_pw";
$ds = ldap_connect($server);
if ($ds) {
ldap_bind($ds, $root_dn, $root_pw);
$search = ldap_search($ds, "my_branch", "(sAMAccountName=".$username.")", array("sn", "givenName"));
$info = ldap_get_entries($ds, $sr);
if($info['count'] > 0) {
$user = $info[0];
return new LdapUser($username, $user['givenName'][0], $user['sn'][0], '???PASSWORD???', '???SALT???', array('ROLE_USER'));
} else {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
ldap_close($ds);
} else {
echo "Connexion au serveur LDAP impossible";
}
}
As you can see, I can't pass the password to my LdapUser class, since it's not accessible through Active Directory.
However, I think it's still possible to authenticate te user, by doing a ldap_bind($username, $password) with the password entered by the user in the login form. The problem is I can't figure out how to access this password in my LdapUserProvider class.
I tried $_POST['password'] but I got an undefined index error...
Any help would be welcome :)
There are a few LDAP Authentication bundles ready to use.
As pointed out below, you can try FR3D/LdapBundle, but it needs FOSUserBundle to work. You can also try IMAG (BorisMorel/LdapBundle), if you don't want to (or don't need to) use FOSUserBundle. BorisMorel uses php-ldap to work, it requires no additional bundle nor any zend components.
Even if you don't want to use them, I'd suggest you check out BorisMorel's implementation for more information. The code's quite simple to understand as it's very straightforward.
Your code sets the authentication state of a connection by transmitting a bind request. Does this code attempt to search for an entry, retrieve the password from that entry and then bind as that distinguished name? If so, even if you could retrieve the password, a properly configured directory server will not allow the transmission of a pre-encoded password. Passwords should always be transmitted in clear text over a secure connection (TLS or SSL) so the server can perform password quality and history validation. Therefore, your code must know the clear-text password beforehand. Alternatively, a LDAP-compliant server might allow the use of a proxied authentication for certain entries.
see also
LDAP: Mastering Search Filters
LDAP: Search best practices
LDAP: Programming practices
Try FR3dLdapBundle
The 2.0.x branch has support with Active Directory if you install Zend\Ldap component from Zend Framework 2
I have an intranet server on a Windows domain (server is Windows 2003, IIS6, NTFS permissions). It is on the domain Domain01. I have users from two domains in the same forest that access this intranet: Domain01 and Domain02 (DCs also running Windows 2003). Currently, the users are required to login by entering either:
Domain01\username or username#Domain01
My users are completely and thoroughly confused by having to enter the domain each time they log in.
Is there any way to simply allow them to log in by entering just their username and password WITHOUT the domain? For example, have the server try Domain01 by default, and if the login fails to try Domain02?
NOTE: I would like to do this via IIS or server settings if possible, rather than programmatically (for reference, I am using ASP.NET 2.0).
Yes. Usually what I do is do a global catalog search using the supplied user name as the sAMAccountName. Doing this with a PrincipalSearcher requires getting the underlying DirectorySearcher and replacing it's SearchRoot. Once I find the corresponding user object I extract the domain from the user object's path and use that as the domain for the authentication step. How you do the authentication varies depending on what you need it to do. If you don't need impersonation you can use PrincipalContext.ValidateCredentials to make sure that the username/password match using a PrincipalContext that matches the domain of the user account that you previously found. If you need impersonation check out this reference.
// NOTE: implement IDisposable and dispose of this if not null when done.
private DirectoryEntry userSearchRoot = null;
private UserPrincipal FindUserInGlobalContext( string userName )
{
using (PrincipalSearcher userSearcher = new PrincipalSearcher())
{
using (PrincipalContext context
= new PrincipalContext( ContextType.Domain ))
{
userSearcher.QueryFilter = new UserPrincipal( context );
DirectorySearcher searcher
= (DirectorySearcher)userSearcher.GetUnderlyingSearcher();
// I usually set the GC path from the existing search root
// by doing some string manipulation based on our domain
// Your code would be different.
string GCPath = ...set GC path..
// lazy loading of the search root entry.
if (userSearchRoot == null)
{
userSearchRoot = new DirectoryEntry( GCPath );
}
searcher.SearchRoot = userSearchRoot;
using (PrincipalContext gcContext =
new PrincipalContext( ContextType.Domain,
null,
GCPath.Replace("GC://",""))
{
UserPrincipal userFilter = new UserPrincipal( gcContext );
userFilter.SamAccountName = userName;
userSearcher.QueryFilter = userFilter;
return userSearcher.FindOne() as UserPrincipal;
}
}
}
}