Sign PDF with new Belgian id card (eid middleware and itext) - itext

I try to sign some pdf's with a Belgian id card.
To acheive that, I'm using the belgium eid middleware to sign the data and itext7 to stamp the pdf with the signature.
I use a PdfSigner (itext) and I have implement a IExternalSignature to call the eid middleware to sign the message.
All work well for the Belgian id card 1.7 with encryption RSA and hash SHA256.
But when I try to sign with the new Belgian id card 1.8 with encryption ECDSA and hash SHA384, the signature can't be validated by adobe reader (or other reader).
"The document has been altered or corrupted".
It seems to be a mismatch somewhere in the hashing or ...
I search for some days but I have no more idea to fix that ...
Someone have an idea about what is going wrong?
Thanks in advance for your help.
Here some additional informations.
The external signature class:
internal sealed class BeIDSignature : IExternalSignature
{
public string GetEncryptionAlgorithm()
{
return eidWrapper.Instance.GetEncryptionAlgorithm().ToString();
}
public string GetHashAlgorithm()
{
switch (EidWrapper.Instance.GetEncryptionAlgorithm())
{
case EidWrapper.EncryptionAmgorithm.RSA:
return DigestAlgorithms.SHA256;
case EidWrapper.EncryptionAmgorithm.ECDSA:
return DigestAlgorithms.SHA384;
default:
return null;
}
}
public byte[] Sign(byte[] message)
{
return EidWrapper.Instance.SignData(message);
}
}
GetEncryptionAlgorithm will return RSA or ECDSA depending of the chip.
The sign method will use the eid-mw packege to generate the signature.
A little piece of code of the sign method of the EidWrapper:
if (key.KeyType.KeyType == CKK.EC)
{
session.SignInit(new Mechanism(CKM.ECDSA_SHA384), key);
return session.Sign(data);
}
else if (key.KeyType.KeyType == CKK.RSA)
{
session.SignInit(new Mechanism(CKM.SHA256_RSA_PKCS), key);
return session.Sign(data);
}
You can find here a zip with 3 pdf files:
The original file
One signed directly with adobe (siganture is ok)
One signed with eid-mw and itext (signature is NOT ok). But remember
that is working for RSA/SHA256 siganture.
https://easyupload.io/yzscsu
Thanks again for your time.

Here is a sample of an external container for the belgian eid smartcard.
The code is not fully implemented but you have a base to make a siganture in ECDSA/SHA384 correctly.
Hope that will help someone :)
internal sealed class BeIdExternalSignatureContainer : IExternalSignatureContainer
{
private IX509Store _crls = null;
private readonly IHttpClientFactory _httpClientFactory;
private int _crlsSize = 0;
public BeIdExternalSignatureContainer(IHttpClientFactory httpClientFactory)
{
this._httpClientFactory = httpClientFactory;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
signDic.Put(PdfName.SubFilter, PdfName.ETSI_CAdES_DETACHED);
}
public int ComputeEstimateSize()
{
// Base on itext estimation
if (this._crlsSize == 0)
{
this.InitializeCrls();
}
return (8192 + this._crlsSize + 4192) * 2 + 2;
}
public byte[] Sign(Stream data)
{
IReadOnlyDictionary<string, byte[]> certificatesBinaries = EidWrapper.Instance.GetCertificateFiles(new string[] {
Constants.SIGNATURE_CERTIFICATE_NAME,
Constants.CA_CERTIFICATE_NAME,
Constants.ROOT_CERTIFICATE_NAME
});
X509Certificate signCertificate = new(X509CertificateStructure.GetInstance(certificatesBinaries[Constants.SIGNATURE_CERTIFICATE_NAME]));
EidWrapper.EncryptionAlgorithms encryptionAlgorithm = EidWrapper.Instance.GetEncryptionAlgorithm();
SignerInfoGenerator signerGenerator = new SignerInfoGeneratorBuilder()
.WithSignedAttributeGenerator(new PadesSignedAttributeGenerator { SigningCertificate = signCertificate })
.WithUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator(this._httpClientFactory))
.Build(new BeIdSignatureFactory(encryptionAlgorithm), signCertificate);
CmsSignedDataGenerator gen = new();
gen.AddSignerInfoGenerator(signerGenerator);
IX509Store x509CertStore = X509StoreFactory.Create(
"Certificate/Collection",
new X509CollectionStoreParameters(certificatesBinaries.Values.Select(b => new X509Certificate(X509CertificateStructure.GetInstance(b))).ToList()));
gen.AddCertificates(x509CertStore);
gen.AddCrls(this.Crls);
CmsProcessableInputStream cmsProcessableInputStream = new(data);
CmsSignedData signedData = gen.Generate(cmsProcessableInputStream, false);
return signedData.GetEncoded();
}
private IX509Store Crls
{
get
{
if (this._crls == null)
{
this.InitializeCrls();
}
return this._crls;
}
}
private void InitializeCrls()
{
this._crlsSize = 0;
List<X509Crl> crls = new();
X509CrlParser crlParser = new();
HttpClient httpClient = this._httpClientFactory.CreateClient();
foreach (var crlUrl in BeIdSignatureConstants.CRL_URL_COLLECTION)
{
using HttpResponseMessage response = httpClient.Send(new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(crlUrl) });
if (response.IsSuccessStatusCode)
{
using MemoryStream ms = new MemoryStream();
response.Content.ReadAsStream().CopyTo(ms);
byte[] crlBytes = ms.ToArray();
this._crlsSize += crlBytes.Length;
crls.Add(crlParser.ReadCrl(crlBytes));
}
}
this._crls = X509StoreFactory.Create(
"CRL/Collection",
new X509CollectionStoreParameters(crls));
}
}
internal class BeIdSignatureFactory : ISignatureFactory
{
private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;
public BeIdSignatureFactory(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
{
this._encryptionAlgorithm = encryptionAlgorithm;
}
public IStreamCalculator CreateCalculator()
{
BeIdSigner signer = new(this._encryptionAlgorithm);
signer.Init(true, null);
return new DefaultSignatureCalculator(signer);
}
public object AlgorithmDetails
{
get
{
return MapAlgorithm(this._encryptionAlgorithm);
}
}
public static AlgorithmIdentifier MapAlgorithm(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
{
switch (encryptionAlgorithm)
{
case EidWrapper.EncryptionAlgorithms.RSA:
return new AlgorithmIdentifier(PkcsObjectIdentifiers.Sha256WithRsaEncryption, DerNull.Instance);
case EidWrapper.EncryptionAlgorithms.ECDSA:
return new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha384, DerNull.Instance);
default:
throw new ArgumentException($"Unsupported encryption algorithm: {encryptionAlgorithm}");
}
}
}
internal class BeIdSigner : ISigner
{
private byte[] _input;
private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;
public BeIdSigner(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
{
this._encryptionAlgorithm = encryptionAlgorithm;
}
public void Init(bool forSigning, ICipherParameters parameters)
{
this.Reset();
}
public void Update(byte input)
{
throw new NotImplementedException("The \"ISigner.Update\" method is not implemented for the \"BeIdSigner\" class.");
}
public void BlockUpdate(byte[] input, int inOff, int length)
{
this._input = input.Skip(inOff).Take(length).ToArray();
}
public byte[] GenerateSignature()
{
return this._encryptionAlgorithm == EidWrapper.EncryptionAlgorithms.ECDSA
? PlainToDer(EidWrapper.Instance.SignData(this._input))
: EidWrapper.Instance.SignData(this._input);
}
public bool VerifySignature(byte[] signature)
{
throw new NotImplementedException("The \"ISigner.VerifySignature\" method is not implemented for the \"BeIdSigner\" class.");
}
public void Reset()
{
this._input = null;
}
public string AlgorithmName => throw new NotImplementedException("The \"ISigner.AlgorithmName\" property is not implemented for the \"BeIdSigner\" class.");
private static byte[] PlainToDer(byte[] plain)
{
int valueLength = plain.Length / 2;
BigInteger r = new(1, plain, 0, valueLength);
BigInteger s = new(1, plain, valueLength, valueLength);
return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
}
}

This answer sums up the comments to the question.
iText and ECDSA signatures
First of all one has to realize that currently (i.e. for iText 7.2.2) ECDSA is not supported by all parts of the iText signing API.
This limitation is mostly due to the iText PdfPKCS7 class used to create and validate CMS signature containers embedded in PDFs. When it is used to create a signature container, the identifier it stores in the signatureAlgorithm field does not take the used hash algorithm into account. E.g. it uses the same value for RSA (with PKCS1 v1.5 padding) signatures, no matter if it's actually SHA1withRSA or SHA256withRSA or whichever combination.
For RSA this is ok because there indeed is an identifier that can be used for all these cases. For DSA it is somewhat ok because in many contexts DSA is limited to use with SHA1 only.
For ECDSA this is not ok, there only are identifiers taking the hash algorithm into account. iText uses the EC public key identifier in all these cases which is simply wrong. The reason why hardly anyone noticed this bug is that Adobe Acrobat validation apparently ignores the contents of this signatureAlgorithm field: You can even write the RSA identifier into this field of an ECDSA signature and the validation succeeds without an indication of a problem.
To create proper ECDSA signatures, therefore, one currently should not use the PdfPKCS7 class. As all the PdfSigner.signDetached methods internally use the PdfPKCS7 class, this in turn means that one must not use them but instead PdfSigner.signExternalContainer. As a consequence, one must not use an IExternalSignature implementation to retrieve one's signature value but instead an IExternalSignatureContainer implementation in which one builds the CMS signature container differently, for example using BouncyCastle classes.
In the case at hand the BeIDSignature implementation of IExternalSignature, therefore, must be replaced accordingly.
For further details please read the section Which Interface to Use of the iText knowledge base article Digital Signing with iText 7.
ECDSA signature formats
There are two major formats in which an ECDSA signature value can be stored, either as a TLV (DER) encoded sequence of two integers or (plain encoding) as the concatenation of fixed length representations of those two integers.
Depending on the used format one has to use specific algorithm identifiers for ECDSA and PLAIN-ECDSA respectively. If one needs a specific identifier, one can convert the signature value from one format to the other.
In the case at hand the Belgian ID card returns the ECDSA signature value in plain format. To use the more common non-PLAIN ECDSA identifiers, one has to convert that value to the DER format. This can be done using this method:
byte[] PlainToDer(byte[] plain)
{
int valueLength = plain.Length / 2;
BigInteger r = new BigInteger(1, plain, 0, valueLength);
BigInteger s = new BigInteger(1, plain, valueLength, valueLength);
return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
}
(X509Certificate2Signature helper method from the code examples of Digital Signing with iText 7)

Some thoughts:
Are you sure that there is an ECDSA key available on the chip ? I had a short look at the documentation (not sure it's up to date - cf. eid-mw github), which only mentions RSA. Additionally if you can sign using RSA/SHA256, having ECDSA support as well would mean that there's a second key pair on the card - I have some doubts about this ;
Try to sign with ECDSA / SHA384 in Adobe Reader using your eID - check whether you can validate the signature ;
Validate the signature online, using the SD-DSS tool: the diagnostic data may help you in pin-pointing what is wrong (e.g. a sha384 digest was generated, but the signature structure mentions sha256 as digest algo).

Related

PKCS#11Interop.X509Store unable to find Private Key

Pkcs11X509Certificate is unable to find the private key in some tokens.
Pkcs11X509Certificate.GetRSAPrivateKey() yields null. Then, when I run SignedXml.ComputeSignature(), I get the following error:
System.Security.Cryptography.CryptographicException: 'Signing key is not loaded.'
Adding the code below (proof of concept) to the Pkcs11X509Certificate.FindKey works.
Basically I removed CKA.CKA_LABEL from the search template attributes and it finds the certificate Private Key.
// Contrary to what PKCS#11 specification suggests, subject of the private key is not readable even after login.
// So if we cannot find private key with subject, we will search for private keys without subject.
if (keyHandle == null)
{
searchTemplate = new List<IObjectAttribute>()
{
session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, keyClass),
session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true),
session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId),
//session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, ckaLabel),
};
foreach (IObjectHandle foundObjectHandle in session.FindAllObjects(searchTemplate))
{
keyHandle = foundObjectHandle;
break;
}
}

itext7: deferred signing not producing a valid signed pdf

I'm trying to use itext7 to sign a pdf by getting the signature from an external entity. I must be missing something because the deferred pdf signing is not valid. Lets start with the code for deferred signing:
public byte[] GetDocHashFromPreparedDocToSign(string pathToOriginalPdf, string pathToPreparedToBeSignedPdf, List<X509Certificate> certificates) {
var pdfSigner = new PdfSigner(new PdfReader(pathToOriginalPdf),
new FileStream(pathToPreparedToBeSignedPdf, FileMode.Create),
new StampingProperties());
pdfSigner.SetFieldName(_signatureFieldname);
var appearance = pdfSigner.GetSignatureAppearance();
appearance.SetPageRect(new Rectangle(144, 144, 200, 100))
.SetPageNumber(1)
.SetCertificate(certificates[0]);
var container = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
pdfSigner.SignExternalContainer(container, 8192);
byte[] sha256SigPrefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
0x05, 0x00, 0x04, 0x20 };
// service needs to receive sha256 prepended
using var stream = File.OpenRead(pathToPreparedToBeSignedPdf);
var data = DigestAlgorithms.Digest(stream, DigestAlgorithms.SHA256);
var totalHash = new byte[sha256SigPrefix.Length + data.Length];
sha256SigPrefix.CopyTo(totalHash, 0);
data.CopyTo(totalHash, sha256SigPrefix.Length);
return totalHash;
}
The method received the path to the original pdf, the path to the temporary pdf that will contain the placeholder for the signature and a list of X509Certificate that is retrieved from the original service. After reserving the space for the signature, the method calculates the file's hash and prepends it with the sha256 prefix (required by the service that will sign the document).
This information is sent to the service which will return the signature. When the signature is retrieved, the following method is called for filling the signature placeholder with the real signature:
public void SignPreparedToBeSignedDoc(string pathToPreparedToBeSignedPdf, string pathToSignedFile, byte[] signature) {
var document = new PdfDocument(new PdfReader(pathToPreparedToBeSignedPdf));
using var writer = new FileStream(pathToSignedFile, FileMode.Create);
var container = new ExternalInjectingSignatureContainer(signature);
PdfSigner.SignDeferred(document, _signatureFieldname, writer, container);
}
EDIT: based on #mkl comment, I've fixed the signing part:
And here's the ExternalInjectingSignatureContainer:
internal class ExternalInjectingSignatureContainer : IExternalSignatureContainer {
private readonly byte[] _signature;
public ExternalInjectingSignatureContainer(byte[] signature) {
_signature = signature;
}
public byte[] Sign(Stream data){
var sgn = new PdfPKCS7(null, _certificates.ToArray(), "SHA256", false);
sgn.SetExternalDigest(_signature, null, "RSA");
return sgn.GetEncodedPKCS7();
}
public void ModifySigningDictionary(PdfDictionary signDic) {
}
}
Even though the code runs without errors, opening the pdf in adobe shows the following error:
EDIT: after fixing the signing code, now the error is different: it will show the signature info but it will say that the file has been changed or is corrupted.
At this point, it seems like the temporary pdf is being generated correctly, but I'm probably missing something...any clues on how I might debug this issue?
Thanks
EDIT: in response to the comments to the solution presented by #mkl, I've tried to update the code. I had a couple more minutes to play with this today and I've tried to follow the presented guidelines, but I'm clearly still missing something.
Before showing the new code, I'd just like to point out that the previous updated version (that whas using 2 IExternalSignatureContainer instances) seemed to be working correctly. ie, opening the signed pdf on adobe would only show me the yellow warning saying that there was something wrong with the signature:
Since the doc is being used with a test chain, it seems like the signing worked ok (though I might be completely wrong).
So, in order to fix the incorrect usage of the container, I've rewritten the code for the IExternalSignatureContainer's Sign methods. Here's the code I've got for the one that prepares the document hash that is going to be sent to the server:
public override byte[] Sign(Stream data) {
// create PCKS7 for getting attributes
var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
// get document hash
DocumentDigest = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
// get attributes
var docBytesHash = sgn.GetAuthenticatedAttributeBytes(DocumentDigest,
PdfSigner.CryptoStandard.CMS,
null,
null);
//prepend sha256 prefix
var totalHash = new byte[_sha256SigPrefix.Length + docBytesHash.Length];
_sha256SigPrefix.CopyTo(totalHash, 0);
docBytesHash.CopyTo(totalHash, _sha256SigPrefix.Length);
DataToSend = totalHash;
return new byte[0];
}
Since I must call the GetEncodedPKCS7 method with the same parameters that were passed to GetAuthenticatedAttributes, I'm also saving the document hash obtained through the Digest method. DataToSend will be sent to the server so that it can return the signature for that hash.
And here's the code for the other IExternalSignatureContainer that will be called for the deferred signing (PdfSigner.SignDeferred):
public byte[] Sign(Stream data) {
// create CMS
var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
// set the signature bytes
sgn.SetExternalDigest(_signature, null, "RSA");
// call GetEncoded with the same parameters as the original GetAuthenticatedAtt...
//_documentHash == DocumentDigest previous sample
var encodedSig = sgn.GetEncodedPKCS7(_documentHash,
PdfSigner.CryptoStandard.CMS,
null,
null,
null);
return encodedSig;
}
Unfortunately, I must be missing something (or lots of things):
Did I completely missed your point?
EDIT: Once again, following #mkl's lead, I was able to make it work. Like he said, you need to hash the GetAuthenticatedAttributeBytes value:
public override byte[] Sign(Stream data) {
// create PCKS7 for getting attributes
var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
// get document hash
DocumentDigest = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
// get attributes
var docBytes = sgn.GetAuthenticatedAttributeBytes(DocumentDigest,
PdfSigner.CryptoStandard.CMS,
null,
null);
// hash dochBytes
using var hashMemoryStream = new MemoryStream(docBytes, false);
var docBytesHash = DigestAlgorithms.Digest(hashMemoryStream,
DigestAlgorithms.SHA256);
//prepend sha256 prefix
var totalHash = new byte[_sha256SigPrefix.Length + docBytesHash.Length];
_sha256SigPrefix.CopyTo(totalHash, 0);
docBytesHash.CopyTo(totalHash, _sha256SigPrefix.Length);
DataToSend = totalHash;
return new byte[0];
}
Thanks again.
There are two apparent issues, you hash the wrong data and you inject a wrong type of signature:
Hashing the wrong data
You calculate the hash to be signed like this:
using var stream = File.OpenRead(pathToPreparedToBeSignedPdf);
var data = DigestAlgorithms.Digest(stream, DigestAlgorithms.SHA256);
This is not correct.
A signed PDF essentially has this structure (read here for more details):
(By the way, the sketch is not 100% correct as the angled bracket delimiters '<' and '>' around the signature value must also not be hashed.)
Your prepared PDF at pathToPreparedToBeSignedPdf has the same structure, merely the "signature value" is not yet an actual signature value but instead a placeholder of 8192 hex-encoded zero bytes (8192 because that's the number you gave in pdfSigner.SignExternalContainer).
As you can see in the sketch, though, the signature value (or in your case, the placeholder) must not be hashed for signing.
The easiest way to retrieve the prepared PDF except the placeholder is inside the IExternalSignatureContainer implementation you use for preparing the PDF as its Sign method as parameter gets a stream containing exactly that. So instead of the ExternalBlankSignatureContainer use something like the ExternalEmptySignatureContainer from this answer:
public class ExternalEmptySignatureContainer : IExternalSignatureContainer
{
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
}
public byte[] Sign(Stream data)
{
// Store the data to sign and return an empty array
Data = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
return new byte[0];
}
public byte[] Data;
}
and after preparing retrieve the byte[] from its Data member:
var container = new ExternalEmptySignatureContainer();
pdfSigner.SignExternalContainer(container, 8192);
byte[] hash = container.Data;
In your case you may have to prepend the sha256SigPrefix to get a complete encoded DigestInfo object.
Injecting a wrong type of signature
Furthermore, considering your screen shot
you apparently inject a signature of a wrong type. You set a subfilter PdfName.Adbe_pkcs7_detached which implies that the signature to embed is a CMS signature container with a single SignerInfo signing the signed bytes of the PDF. The error message indicates, though, that the signature container you embed either is broken or is not a CMS signature container to start with.
After your edit: Injecting a signature container with incorrect contents
To fix the previous issue, "Injecting a wrong type of signature", you changed your ExternalInjectingSignatureContainer to build a CMS signature container for your signature bytes like this:
var sgn = new PdfPKCS7(null, _certificates.ToArray(), "SHA256", false);
sgn.SetExternalDigest(_signature, null, "RSA");
return sgn.GetEncodedPKCS7();
This, unfortunately, uses the PdfPKCS7 class incorrectly resulting in a CMS signature container with incorrect data.
(Correction: The resulting CMS container is not incorrect per se, but it is structurally extremely simply. Nowadays many profiles - e.g. European PAdES - require additional, signed attributes, and to satisfy such profiles you have to fix as described in the following text. If you only need Adobe Reader to show valid, though, the code above suffices for you.)
To fix you can either indeed build a CMS signature container yourself, using the iText PdfPKCS7 class or other means (like BouncyCastle or built-in .Net classes), or you can let iText do that for you.
The easiest approach is the latter one. You actually don't need deferred signing here at all, you simply implement IExternalSignature so that it calls your remote service:
public class RemoteSignature : IExternalSignature
{
public virtual byte[] Sign(byte[] message) {
IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
byte[] digestInfo = [... prefix messageHash with sha256SigPrefix ...];
//
// Request signature for DigestInfo digestInfo
// and return signature bytes
//
return CALL_YOUR_SERVICE_FOR_SIGNATURE_OF_HASH(digestInfo);
}
public virtual String GetHashAlgorithm() {
return "SHA-256";
}
public virtual String GetEncryptionAlgorithm() {
return "RSA";
}
}
Now you can simply sign doing
var pdfSigner = new PdfSigner(new PdfReader(pathToOriginalPdf),
new FileStream(pathToSignedFile, FileMode.Create),
new StampingProperties());
pdfSigner.SetFieldName(_signatureFieldname);
var appearance = pdfSigner.GetSignatureAppearance();
appearance.SetPageRect(new Rectangle(144, 144, 200, 100))
.SetPageNumber(1)
.SetCertificate(certificates[0]);
var signature = new RemoteSignature();
pdfSigner.SignDetached(signature, certificates, null, null, null, 0, PdfSigner.CryptoStandard.CMS);

How to implement security Authorization using scala and play?

I am using scala and play framework. I want to use play security Authorization in my app.
Previously I implemented it in project using java and play like following :
public class Secured extends Security.Authenticator {
private static String EMAIL = "Email";
private static String U_COOKIE = "ucookie";
public String getUsername(Context ctx) {
String decodedText = null;
String CHARSET = "ISO-8859-1";
Cookies cookies = play.mvc.Controller.request().cookies();
try {
Cookie emailCookie = cookies.get(EMAIL);
Cookie uCookie = cookies.get(U_COOKIE);
if (uCookie !=null && uCookie.value() != null) {
String userId = uCookie.value();
}
if (emailCookie != null && emailCookie.value() != null) {
String email = emailCookie.value();
try {
decodedText = new String(Base64.decodeBase64(email.getBytes(CHARSET)));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
Logger.error(e.getMessage());
}
return decodedText;
}
public Result onUnauthorized(Context ctx) {
String done = play.mvc.Controller.request().path();
return redirect(routes.RegController.signIn(done));
}
}
and I used above Authorization in all of my method using
#Security.Authenticated(Secured.class)
Before any of my methods throughout my application.
When I call any method #before that method gives call to secured class and authenticate user.
Now I want to implement same thing using scala. Following are my questions....
1) Is it possible to use # to inherit and call methods of secured class??
2) What is the right method to call play's security authentication??
P.S. I want to use cookies for implementation of security Authentication/Authorization.
Any help or workaround will be great favor..
If you build an application intended for production:
Don't do it
Use one of the many frameworks out there:
Deadbolt2 : https://github.com/schaloner/deadbolt-2
SecureSocial: http://www.securesocial.ws/
Silhouette : http://silhouette.mohiva.com/
They are also a great starting point to look for best practices.
If you want to do it mainly for learning and there are no real scecurity concerns go for:
https://www.playframework.com/documentation/2.3.x/ScalaActionsComposition
There look for the heading auth it gives some information how to do it.
To have the authentication kick in before any method you could use a Filter to intercept the request:
https://www.playframework.com/documentation/2.3.x/ScalaInterceptors

Implement Custom Authentication In Windows Azure Mobile Services

Windows Azure Mobile Services currently doesn't have an option for custom authentication and looking at the feature request
http://feedback.azure.com/forums/216254-mobile-services/suggestions/3313778-custom-user-auth
It isn't coming anytime soon.
With a .NET backend and a .NET application how do you implement custom authentication, so that you don't have to use Facebook, Google or any of their other current providers?
There are plenty of partially completed tutorials on how this this is done with a JS backend and iOS and Android but where are the .NET examples?
I finally worked through the solution, with some help of the articles listed below, some intellisense and some trial and error.
How WAMS Works
First I wanted to describe what WAMS is in a very simple form as this part confused me for a while until it finally clicked. WAMS is just a collection of pre-existing technologies packaged up for rapid deployment. What you need to know for this scenario is:
As you can see WAMS is really just a container for a WebAPI and other things, which I won't go into detail here. When you create a new Mobile Service in Azure you get to download a project that contains the WebAPI. The example they use is the TodoItem, so you will see code for this scenario through the project.
Below is where you download this example from (I was just doing a Windows Phone 8 app)
I could go on further about this but this tutorial will get you started:
http://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-windows-store-dotnet-get-started/
Setup WAMS Project
You will need your MasterKey and ApplicationKey. You can get them from the Azure Portal, clicking on your Mobile Services App and pressing Manage Keys at the bottom
The project you just downloaded, in the Controllers folder I just created a new controller called AccountController.cs and inside I put
public HttpResponseMessage GetLogin(String username, String password)
{
String masterKey = "[enter your master key here]";
bool isValidated = true;
if (isValidated)
return new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("{ 'UserId' : 'F907F58C-09FE-4F25-A26B-3248CD30F835', 'token' : '" + GetSecurityToken(new TimeSpan(1,0, 0), String.Empty, "F907F58C-09FE-4F25-A26B-3248CD30F835", masterKey) + "' }") };
else
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Username and password are incorrect");
}
private static string GetSecurityToken(TimeSpan periodBeforeExpires, string aud, string userId, string masterKey)
{
var now = DateTime.UtcNow;
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var payload = new
{
exp = (int)now.Add(periodBeforeExpires).Subtract(utc0).TotalSeconds,
iss = "urn:microsoft:windows-azure:zumo",
ver = 2,
aud = "urn:microsoft:windows-azure:zumo",
uid = userId
};
var keyBytes = Encoding.UTF8.GetBytes(masterKey + "JWTSig");
var segments = new List<string>();
//kid changed to a string
var header = new { alg = "HS256", typ = "JWT", kid = "0" };
byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
segments.Add(Base64UrlEncode(headerBytes));
segments.Add(Base64UrlEncode(payloadBytes));
var stringToSign = string.Join(".", segments.ToArray());
var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
SHA256Managed hash = new SHA256Managed();
byte[] signingBytes = hash.ComputeHash(keyBytes);
var sha = new HMACSHA256(signingBytes);
byte[] signature = sha.ComputeHash(bytesToSign);
segments.Add(Base64UrlEncode(signature));
return string.Join(".", segments.ToArray());
}
// from JWT spec
private static string Base64UrlEncode(byte[] input)
{
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}
You can replace what is in GetLogin, with your own validation code. Once validated, it will return a security token (JWT) that is needed.
If you are testing on you localhost, remember to go into your web.config file and fill in the following keys
<add key="MS_MasterKey" value="Overridden by portal settings" />
<add key="MS_ApplicationKey" value="Overridden by portal settings" />
You need to enter in your Master and Application Keys here. They will be overridden when you upload them but they need to be entered if you are running everything locally.
At the top of the TodoItemController add the AuthorizeLevel attribute as shown below
[AuthorizeLevel(AuthorizationLevel.User)]
public class TodoItemController : TableController<TodoItem>
You will need to modify most of the functions in your TodoItemController but here is an example of the Get All function.
public IQueryable<TodoItem> GetAllTodoItems()
{
var currentUser = User as ServiceUser;
Guid id = new Guid(currentUser.Id);
return Query().Where(todo => todo.UserId == id);
}
Just a side note I am using UserId as Guid (uniqueidentifier) and you need to add this to the todo model definition. You can make the UserId as any type you want, e.g. Int32
Windows Phone/Store App
Please note that this is just an example and you should clean the code up in your main application once you have it working.
On your Client App
Install NuGet Package: Windows Azure Mobile Services
Go into App.xaml.cs and add this to the top
public static MobileServiceClient MobileService = new MobileServiceClient(
"http://localhost:50527/",
"[enter application key here]"
);
In the MainPage.xaml.cs I created
public class Token
{
public Guid UserId { get; set; }
public String token { get; set; }
}
In the main class add an Authenticate function
private bool Authenticate(String username, String password)
{
HttpClient client = new HttpClient();
// Enter your own localhost settings here
client.BaseAddress = new Uri("http://localhost:50527/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync(String.Format("api/Account/Login?username={0}&password={1}", username, password)).Result;
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var token = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(response.Content.ReadAsStringAsync().Result);
App.MobileService.CurrentUser = new MobileServiceUser(token.UserId.ToString());
App.MobileService.CurrentUser.MobileServiceAuthenticationToken = token.token;
return true;
}
else
{
//Something has gone wrong, handle it here
return false;
}
}
Then in the Main_Loaded function
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
Authenticate("test", "test");
RefreshTodoItems();
}
If you have break points in the WebAPI, you will see it come in, get the token, then come back to the ToDoItemController and the currentUser will be filled with the UserId and token.
You will need to create your own login page as with this method you can't use the automatically created one with the other identity providers. However I much prefer creating my own login screen anyway.
Any other questions let me know in the comments and I will help if I can.
Security Note
Remember to use SSL.
References
[] http://www.thejoyofcode.com/Exploring_custom_identity_in_Mobile_Services_Day_12_.aspx
[] http://www.contentmaster.com/azure/creating-a-jwt-token-to-access-windows-azure-mobile-services/
[] http://chrisrisner.com/Custom-Authentication-with-Azure-Mobile-Services-and-LensRocket
This is exactly how you do it. This man needs 10 stars and a 5 crates of beer!
One thing, I used the mobile Service LoginResult for login like:
var token = Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
Hope to get this into Android now!

GCM Cloud Connection Server doesn't respond to SASL on XMPP

Using agsXMPP to connect to Google Cloud Messaging XMPP API for the purpose of sending notification to Android devices.
The connection is established OK, but on SASL start, after sending the PLAIN auth element, the server stops responding, and closes the connection after a further 20 seconds.
Base64 decoding the auth example from the documentation page (http://developer.android.com/google/gcm/ccs.html) shows login values of:
126200347933#projects.gcm.android.com12620034793#projects-ga-.android.comAIzaSyB3rcZNkfnqKdFb9mhzCBiYpORDA2JWWtw
Where as agsXMPP is (correctly I think) encoding the string, to give something like:
[ProjectID]\40gcm.googleapis.com[**API*KEY*PASSWORD**]
Note the \40 in my version instead of the # in the Google example - could this make a difference?
I'm expecting either a success or failure message, no response at all is difficult to debug. Could this at character be responsible for some failure, or does Google's implementation of XMPP just not provide the correct responses.
UPDATED:
I answered below, essentially, yes, Google can't handled the encoded # character because it doesn't support that XMPP extension.
After some more testing, I added a new SaslFactory mechanism in agsXMPP and bound it to use the username without encoding (part of extension http://xmpp.org/extensions/xep-0106.html, which Google doesn't support), and then on SaslStartEvent - specify that I want to use that mechanism instead of the inbuilt plain one. - and now the connection will continue normally.
xmpp = new XmppClientConnection();
xmpp.UseSSL = true;
xmpp.UseStartTLS = false;
xmpp.Server = "gcm.googleapis.com";
xmpp.ConnectServer = "gcm.googleapis.com";
xmpp.Port = 5235;
/* Other connection settings /*
SaslFactory.AddMechanism("MyPLAINMechanism", typeof(MyPlainMechanismClass));
xmpp.OnSaslStart += (sender, args) =>
{
args.Auto = false;
args.Mechanism = "MyPLAINMechanism";
args.ExtentedData = new GcmPlainSaslExtendedData
{
Username = "MY UNENCODED USERNAME"
};
};
Then we define the MyPlainMechanismClass which inherits from the Mechanism in agsXMPP, the source code is the same as the original PlainSaslMechanism except the line where the username is input - you can pass in an unencoded username using the ExtendedData property on args.
public class MyPlainMechanismClass: Mechanism
{
private XmppClientConnection m_XmppClient = null;
public GcmPlainSaslMechanism()
{
}
public override void Init(XmppClientConnection con)
{
m_XmppClient = con;
// <auth mechanism="PLAIN" xmlns="urn:ietf:params:xml:ns:xmpp-sasl">$Message</auth>
m_XmppClient.Send(new agsXMPP.protocol.sasl.Auth(agsXMPP.protocol.sasl.MechanismType.PLAIN, Message()));
}
public override void Parse(Node e)
{
// not needed here in PLAIN mechanism
}
private string Message()
{
// NULL Username NULL Password
StringBuilder sb = new StringBuilder();
//sb.Append( (char) 0 );
//sb.Append(this.m_XmppClient.MyJID.Bare);
sb.Append((char)0);
//sb.Append(this.Username);
sb.Append(((GcmPlainSaslExtendedData) this.ExtentedData).Username);
sb.Append((char)0);
sb.Append(this.Password);
byte[] msg = Encoding.UTF8.GetBytes(sb.ToString());
return Convert.ToBase64String(msg, 0, msg.Length);
}
}
Our custom ExtendedData object which we use to pass in custom arguments, such as an unencoded username in this case.
public class GcmPlainSaslExtendedData : agsXMPP.Sasl.ExtendedData
{
public string Username { get; set; }
}