Get complete certificate chain including the root certificate - ssl

How do I get complete certificate chain for a server? Though some claim one should be able to do just that with openssl s_client -showcerts, it turns not always to be the case.
echo | openssl s_client -CApath /etc/ssl/certs -connect www.ssllabs.com:443 \
-showcerts | grep -B2 BEGIN
depth=3 C = SE, O = AddTrust AB, OU = AddTrust External TTP Network, CN = AddTrust External CA Root
verify return:1
depth=2 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Certification Authority
verify return:1
depth=1 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
verify return:1
depth=0 OU = Domain Control Validated, OU = PositiveSSL, CN = www.ssllabs.com
verify return:1
0 s:/OU=Domain Control Validated/OU=PositiveSSL/CN=www.ssllabs.com
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
-----BEGIN CERTIFICATE-----
--
1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
-----BEGIN CERTIFICATE-----
--
2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
-----BEGIN CERTIFICATE-----
DONE
Here we have three certificates our of four. All except of the AddTrust External CA Root certificate. (Possibly because it is not included into the certificate bundle. And not like this is required. And yes, I can find the missing one at /etc/ssl/certs)
How do I get all certificates for a server in a fully automatic fashion?

Meta: I tried to answer this in superuser but you deleted it. Fortunately when I found this copy most of my work was still sitting in a scratch notepad I hadn't closed, otherwise I wouldn't have been willing to do the research work twice.
s_client -showcerts shows the certs sent by the server; according to the RFCs, this should be a valid chain in upward order except that the root MAY (in RFC2119 definition i.e. allowed but not particularly recommended) be omitted. However, not all servers are configured correctly, and some may send extra, missing, and/or out-of-order certs. Also depending on the CA used there may be more than one valid chain but the server can only send one. openssl currently will use only the chain sent, but this will change soon in 1.0.2, and other reliers already sometimes find a different chain than the one sent.
openssl: if the received chain is complete up to and maybe including a root which is in the truststore used (whose default location depends on system or build, and in any case can always be overridden) then openssl client will validate it as okay -- unless it is expired, or revoked and that info is available which usually it isn't. In that case you can write a client program that connects after setting a cert-verify callback function that outputs the full certs as processed by the validation loop, or other info from them you want, as opposed to s_client which uses a callback that logs (only) the subject name in the depth=n lines, which you can see in your example includes all 4 certs in the chain here. openssl is opensource, so a client program that does things mostly like s_client could be a modified copy of s_client (in this case specifically s_cb.c).
Java can also do this and is a good bit shorter to write, but requires Java be installed. If the received chain validates against an anchor in the truststore used (which defaults to a set of public roots but can be modified or overridden, and can have non-root anchors) you similarly can write a program (maybe 20 lines) to connect using a HandshakeCompletedListener which dumps the info from event.getPeerCertificates(). However if the chain doesn't validate, Java aborts the handshake with an exception and you get no certs at all, unlike the openssl case where you might get partial information before the error occurs -- plus openssl's checking, at least by default, isn't quite as strict anyway.
UPDATE: for completeness, in Java 7+, commandline keytool -printcert -sslserver displays the chain sent, in a rather cluttered format.
Among the browsers I can easily check, Firefox and Chrome on Windows (at least) can write out the chain they found and validated. ISTR but can't easily retest the Firefox error/exception dialog can also do this for a chain that fails to validate and may be incomplete. These are not automatic as-is, but I've seen ads for numerous "simulate GUI user input" tools that apparently could drive them as desired.

You get the chain including the builtin trusted root certificate inside the verify_callback (see SSL_CTX_set_verify. With a small Perl program you can dump the chain like this:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::SSL;
IO::Socket::SSL->new(
PeerHost => 'www.google.com:443',
SSL_verify_callback => sub {
my $cert = $_[4];
my $subject = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($cert));
my $issuer = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($cert));
print "# $subject (issuer=$issuer)\n";
print Net::SSLeay::PEM_get_string_X509($cert),"\n";
return 1;
}
) or die $SSL_ERROR||$!;

Related

Understanding openssl. Where is the cert file?

I am using the command ...
openssl s_client -showcerts -connect reds-cluster-01:443
And I get the output:
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = RapidSSL TLS DV RSA Mixed SHA256 2020 CA-1
verify return:1
depth=0 CN = *.my-co-example.com
verify return:1
CONNECTED(00000003)
---
Certificate chain
0 s:/CN=*.my-co-example.com
i:/C=US/O=DigiCert Inc/CN=RapidSSL TLS DV RSA Mixed SHA256 2020 CA-1
-----BEGIN CERTIFICATE-----
MIIGnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxgkqhkiG9w0BAQsFADBZ
...
I assume that means somewhere on the filesystem of my server there would be a file somewhere that has the string ...
MIIGnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxgkqhkiG9w0BAQsFADBZ
... in it. How can I find that file without having to execute something like?
sudo grep -sr MIIGnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxgkqhkiG9w0BAQsFADBZ /
I assume that means somewhere on the filesystem of my server there would be a file somewhere that has the string ...
This assumption is wrong. What you see is part of the server certificate, which need to be checked against a CA certificate located in the local trust store. The server certificate is typically not in the local trust store. See SSL Certificate framework 101: How does the browser actually verify the validity of a given server certificate? for more on certificates are checked.
You can see more with "CURL -W CERTS" from Daniel Stenberg (also on Stack Overflow), which works on curl for the past 25+ years.
When a client connects to a TLS server it gets sent one or more certificates during the handshake.
Those certificates are verified by the client, to make sure that the server is indeed the right one: the server the client expects it to be; no impostor and no man in the middle etc.
When such a server certificate is signed by a Certificate Authority (CA), that CA’s certificate is normally not sent by the server but the client is expected to have it already in its CA store.
(So no file to check on the filesystem)
Ever since the day SSL and TLS first showed up in the 1990s user have occasionally wanted to be able to save the certificates provided by the server in a TLS handshake.
The openssl tool has offered this ability since along time and is actually one of my higher ranked stackoverflow answers.
Now (for curl 7.88.0,to be shipped in February 2023), Daniel proposes:
Using the –write-out (-w) option and the new variables %{certs} and %{num_certs}, curl can now do what you want.
Get the certificates from a server in PEM format:
$ curl https://curl.se -w "%{certs}" -o /dev/null > cacert.pem
$ curl --cacert cacert.pem https://curl.se/
That is easier to parse than the openssl s_client -showcerts -connect current alternative.

Another situation where I get X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY

As a followup to this original question:
Unable to get certificate locally
I did solve the original problem as jww answered.
And I have now followed the same steps of importing the certificate chain for our company site, from "comodo". I simply added them to the file which I originally used with the "google.com" root certs.
Now, although this still works fine with "google", when I connect to our company website, I still get the error code 20 on the SSL_get_verify_result() call.
Is this a result of our using a "wildcard" certificate? i.e.: *.domain.com.
The version of OpenSSL I am currently using is 1.0.1g.
I don't see any other differences from my perspective.
Thanks for any advice.
----- Updated ------
First, let me comment that I am not mentioning our domain, and not posting too much material from the OpenSSL command, as I am not familiar enough with what should be kept confidential.
What I did was combine the base64 encoded certificates into one big file, as the previous post instructed. And I obtained them via the browser "export" utility in the same manner for both. That means the certificates that we use, as well as the google certificates from my previous post are all concatenated. Specifically it looks like this now:
Our Company Cert
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
and it is signed by these guys - ComodoRSA
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
and that is signed at the root here - ComodoRoot
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
and this is the GOOGLE G3 who signed the "www.google.com"
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
and the GOOGLE G3 is signed by this one - globalSign
-----BEGIN CERTIFICATE-----
....
-----END CERTIFICATE-----
Then the code segment I use to test looks like this:
strcpy(host,"our.domain.com");
// strcpy(host,"www.google.com");
/* Build our SSL context*/
ctx = initialize_ctx(KEYFILE,NULL);
/* Connect the TCP socket*/
sock = tcp_connect(host,port);
And then later...
result = SSL_get_verify_result(ssl);
switch(result) {
case X509_V_ERR_CERT_HAS_EXPIRED : break;
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN : break;
case X509_V_OK : break;
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT : break;
default :puts("Certificate doesn't verify");
}
Simply put, this same code, using this same CRT file, does not give me a "20" error when I use the www.google.com" host, but does give me an error "20" when I use our server. The extent of the test involves changing that commented out name of the host.
Connections to the HTTPS server with commercial clients (Chrome, IE, FF...) have no errors.
As for the comment that recommended the command, I get the following (hopefully I pasted the necessary information):
For Google:
depth=2 OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign verify return:1
depth=1 C = US, O = Google Trust Services, CN = Google Internet Authority G3
verify return:1
depth=0 C = US, ST = California, L = Mountain View, O = Google LLC, CN = www.google.com
verify return:1
read:errno=0
---- other stuff ----
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=www.google.com
i:/C=US/O=Google Trust Services/CN=Google Internet Authority G3
1 s:/C=US/O=Google Trust Services/CN=Google Internet Authority G3
i:/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign
---
And for our domain, I am getting the following (company specifics hidden):
depth=2 C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Certification Authority
verify error:num=20:unable to get local issuer certificate
verify return:0
read:errno=10054
---- other stuff ---
Certificate chain
0 s:/C=US/postalCode=00000/ST=IL/L=city/street=main/O=company./OU=PremiumSSL Wildcard/CN=*.domain.com
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Organization Validation Secure Server CA
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
----
Now, the chain is that our certificate is signed by "ComodoRSA", and that it is signed by "ComodoRoot".
However, as I originally indicated, our certificate is a "wildcard" certificate, where the google certificate isn't.
So that was my question: Is there a problem using wild card certificates with the version of openSSL 1.0.1g?
--- EDIT2 ----
I am adding more content to the post, so I can include an image from the browser.
Our certificate is on a live web site, and is not self signed.
I am checking the common name in a portion of the code not shown. In this post I am only hoping for advice on this error.
I found a site that uses the same chain we use: DrudgeReport.com
I simply extracted the certificates with the browser, and saved them into a file. This was identical to the steps I used form the google.com site. (View certificate and copy to file)
The result from Drudge is error 19 which is "self signed", not error 20 which was my error. The root level (comodo secure) is the same when I copy to file from either site (as I would expect).
As I learn this from your comments and the link, I see the next intermediate question is: how do I know what certificates are being sent by the server, and which were in the store? Then I would know which to import. What, if any, is the indicator from the openssl command output you provided?
The educational material on openssl and this is rather difficult to find. Just a lot of doc, which to a novice such as myself, is rather circular in it's definitions.
Thanks for your feedback so far.
So that was my question: Is there a problem using wild card certificates with the version of openSSL 1.0.1g?
It is not a problem of wildcard certificates. In fact, your code does not even check the subject of the certificate at all (i.e. your code is insecure), it mostly checks the certificate chain, expiration and purpose of the certificate. And the error message from openssl s_client clearly points out the problem:
verify error:num=20:unable to get local issuer certificate
Thus, the problem is not the certificate subject but that it cannot find a local trust anchor. Looking at the certificate chain provided by your server gives the following certificate chain:
[1] CN=*.domain.com, issued by [2]
[2] CN=COMODO RSA Organization Validation Secure Server CA, issued by [3]
[3] CN=COMODO RSA Certification Authority, issued by [ROOT]
The expected [ROOT] is "CN=AddTrust External CA Root" - only this CA is not in your list of trusted root CA.
While you give not the detailed names of the certificates you have in your local trust store my guess is that the CA you call "ComodoRoot" is similar to what I have as "[3] CN=COMODO RSA Certification Authority" in the list. Only, in your trust store it is likely the self-signed version of the certificate while in the certificate chain provided by the server it is a certificate issued by "[ROOT] CN=AddTrust External CA Root". Both certificates have the same public and private key which means that the signatures in certificate chain can be successfully validated with both.
But, the old (and long unsupported version) of OpenSSL you are using can not properly deal with this kind of situation. It will follow the certificate chain send by the server and then expect the last certificate in the chain signed by on of the certificates in your trust store. If this fails it will not check if a shorter chain might be validated successfully instead.
This means it will succeed if you either have "CN=AddTrust External CA Root" in your trust store or if the server sends a short chain which ends with "[2] CN=COMODO RSA Organization Validation Secure Server CA" since then it will find the issuer for this (your "ComodoRoot", i.e. "CN=COMODO RSA Certification Authority") in your trust store.
For a more detailed explanation of this problem see this answer at stackoverflow.com or this article. Note that there is no way to fix this in code with OpenSSL 1.0.1 - you need to either add the missing certificate to your trust store or make changes to the certificate chain send by the server.

Openssl verify with chained CA and chained Cert

I have a certificate chain as:
root CA -> intermediate CA -> org CA -> client Cert
When I verify the client cert with CA as root CA -> intermediate CA -> org CA, it works:
$ cat org_1_ca/ca_crt.pem intermediate_ca/ca_crt.pem root_ca/ca_crt.pem > /tmp/test123.pem
$ openssl verify -CAfile /tmp/test123.pem client/client_crt.pem
client_crt.pem: OK
But when I chained my client cert with org CA (org CA -> client Cert), and have the rest of the chain as CA (root CA -> intermediate CA), it doesn't:
$ cat intermediate_ca/ca_crt.pem root_ca/ca_crt.pem > /tmp/test12.pem
$ openssl verify -CAfile /tmp/test12.pem client/org1_client_crt.pem
client/org1_client_crt.pem: C = US, ST = CA, L = LA, O = PP, OU = TEST, CN = user
error 20 at 0 depth lookup:unable to get local issuer certificate
Is this something that fundamentally wrong or openssl verify doesn't like that? I tried the same thing with nginx and and openssl connect and there was no luck there. Any help is appreciated.
The openssl commandline verify operation reads only one certificate, the first one, from the file given as operand, or from each file if more than one is given. This differs from the files specified with the -CAfile -trusted -untrusted options which can (and typically do) contain multiple certs.
Your file client/org1_client_crt.pem presumably contains the client cert and the 'org CA' cert, in that order. Only the client cert is used, the 'org CA' cert is ignored, and as a result you do not have a valid chain to verify.
If you want to use commandline to mimic/test the validation that a receiver (for a client cert, the server) would do, supply the leaf cert as the operand and all other transmitted (chain) certs with -untrusted, and the anchor(s) plus any 'known' intermediates in the truststore either explicit or defaulted.
There is no openssl connect operation; I assume you mean openssl s_client with options including -connect since that's one place it would make sense to use a client cert chain. The -cert option to s_client similarly uses only the first cert in the file. There is no option on commandline to specify the client chain except in the most recent version, 1.1.0, and even there it isn't documented so you have to read the help message carefully or the code, although the API/library has long supported this for code you write yourself.
Through 1.0.2 if you want to send a client cert with full chain to the server (as you should per the RFCs), assuming the server requests client authentication which is not usual and not the default for nginx (among others), you have to use a trick: supply all the certs needed for the client chain in the truststore, in addition to the anchor(s) needed to verify the server, either using -CAfile and/or -CApath explicitly, or using (modifying if needed) the default truststore unless your openssl is an older non-RedHat version where the default truststore didn't work in s_client s_server s_time only.
And the same is true about the server cert/chain in s_server except that it is used almost always instead of very rarely.

show entire certificate chain for a local certificate file

I have a certificate (for example this one) saved in a local file. Using openssl from the command line, how can I display the entire chain from this certificate to a root CA? I tried:
openssl verify -verbose -purpose sslserver -CApath /etc/ssl/certs InCommonServerCA.txt
and got this confusing output that only seems to show the leaf certificate:
InCommonServerCA.txt: C = US, O = Internet2, OU = InCommon, CN = InCommon Server CA
error 26 at 0 depth lookup:unsupported certificate purpose
OK
Any ideas?
For local certificates you can see the subject and direct issuer using:
openssl x509 -noout -subject -issuer -in test.crt
subject= /C=US/ST=Utah/L=SLC/O=My Organization/CN=my.server.com
issuer= /C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
But that doesn't indicate if the certificate includes any intermediate certificates or the full chain of trust. The verify command you listed will fail if your system cannot validate the chain (example: you are missing an intermediate certificate or the root is not trusted), showing an error message like:
error 20 at 0 depth lookup:unable to get local issuer certificate
If you want to verify each entry in the file, you can use this script to show the chain of trust for a local certificate:
~ % ssl_chain.sh google.crt
0: subject= /C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
issuer= /C=US/O=Google Inc/CN=Google Internet Authority G2
1: subject= /C=US/O=Google Inc/CN=Google Internet Authority G2
issuer= /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
2: subject= /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
issuer= /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
google.crt: OK
If you want to verify the chain and purpose, your openssl command is correct. The "OK" indicates the chain verifies. The error indicates there is an issue with that certificate being used for an sslserver purpose. It looks like your certificate is a CA cert, not a leaf cert.
What kind of chain info are you trying to display? You could look at the subject and issuer fields to show chaining. The verify command you used above proves that the one cert signed the other cert.

OpenSSL: unable to verify the first certificate for Experian URL

I am trying to verify an SSL connection to Experian in Ubuntu 10.10 with OpenSSL client.
openssl s_client -CApath /etc/ssl/certs/ -connect dm1.experian.com:443
The problem is that the connection closes with a Verify return code: 21 (unable to verify the first certificate).
I've checked the certificate list, and the Certificate used to sign Experian (VeriSign Class 3 Secure Server CA - G3) is included in the list.
/etc/ssl/certs/ca-certificates.crt
Yet I don't know why it is not able to verify the first certificate.
The entire response could be seen here:
https://gist.github.com/1248790
The first error message is telling you more about the problem:
verify error:num=20:unable to get local issuer certificate
The issuing certificate authority of the end entity server certificate is
VeriSign Class 3 Secure Server CA - G3
Look closely in your CA file - you will not find this certificate since it is an intermediary CA - what you found was a similar-named G3 Public Primary CA of VeriSign.
But why does the other connection succeed, but this one doesn't? The problem is a misconfiguration of the servers (see for yourself using the -debug option). The "good" server sends the entire certificate chain during the handshake, therefore providing you with the necessary intermediate certificates.
But the server that is failing sends you only the end entity certificate, and OpenSSL is not capable of downloading the missing intermediate certificate "on the fly" (which would be possible by interpreting the Authority Information Access extension). Therefore your attempt fails using s_client but it would succeed nevertheless if you browse to the same URL using e.g. FireFox (which does support the "certificate discovery" feature).
Your options to solve the problem are either fixing this on the server side by making the server send the entire chain, too, or by passing the missing intermediate certificate to OpenSSL as a client-side parameter.
Adding additional information to emboss's answer.
To put it simply, there is an incorrect cert in your certificate chain.
For example, your certificate authority will have most likely given you 3 files.
your_domain_name.crt
DigiCertCA.crt # (Or whatever the name of your certificate authority is)
TrustedRoot.crt
You most likely combined all of these files into one bundle.
-----BEGIN CERTIFICATE-----
(Your Primary SSL certificate: your_domain_name.crt)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(Your Intermediate certificate: DigiCertCA.crt)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(Your Root certificate: TrustedRoot.crt)
-----END CERTIFICATE-----
If you create the bundle, but use an old, or an incorrect version of your Intermediate Cert (DigiCertCA.crt in my example), you will get the exact symptoms you are describing.
SSL connections appear to work from browser
SSL connections fail from other clients
Curl fails with error: "curl: (60) SSL certificate : unable to get local issuer certificate"
openssl s_client -connect gives error "verify error:num=20:unable to get local issuer certificate"
Redownload all certs from your certificate authority and make a fresh bundle.
I came across the same issue installing my signed certificate on an Amazon Elastic Load Balancer instance.
All seemed find via a browser (Chrome) but accessing the site via my java client produced the exception javax.net.ssl.SSLPeerUnverifiedException
What I had not done was provide a "certificate chain" file when installing my certificate on my ELB instance (see https://serverfault.com/questions/419432/install-ssl-on-amazon-elastic-load-balancer-with-godaddy-wildcard-certificate)
We were only sent our signed public key from the signing authority so I had to create my own certificate chain file. Using my browser's certificate viewer panel I exported each certificate in the signing chain. (The order of the certificate chain in important, see https://forums.aws.amazon.com/message.jspa?messageID=222086)
Here is what you can do:-
Exim SSL certificates
By default, the /etc/exim.conf will use the cert/key files:
/etc/exim.cert
/etc/exim.key
so if you're wondering where to set your files, that's where.
They're controlled by the exim.conf's options:
tls_certificate = /etc/exim.cert
tls_privatekey = /etc/exim.key
Intermediate Certificates
If you have a CA Root certificate (ca bundle, chain, etc.) you'll add the contents of your CA into the exim.cert, after your actual certificate.
Probably a good idea to make sure you have a copy of everything elsewhere in case you make an error.
Dovecot and ProFtpd should also read it correctly, so dovecot no longer needs the ssl_ca option.
So for both cases, there is no need to make any changes to either the exim.conf or dovecot.conf(/etc/dovecot/conf/ssl.conf)
If you are using MacOS use:
sudo cp /usr/local/etc/openssl/cert.pem /etc/ssl/certs
after this Trust anchor not found error disappears
For those using zerossl.com certificates, drag and drop all certificates (as is) to their respective folders.
Cut and pasting text into existing files, may cause utf8 issues - depending upon OS, format and character spacings.