I'm trying to build an application in such a way that NodeJS serves as the backend with all business logic exposing JSON REST services to be consumed by the angular 4 app which is nothing but a dumb client. So far so good, however I'm having a hard time figuring the Session management.
I found that token based authentication is a way to go since you might one day be serving mobile apps however, I have a problem: if I go with JSONWebToken strategy on the server side with token expiration set to half an hour, then my client will need to re authenticate it self after half an hour which doesn't seem like a good fit, because then it may force the user to sign in again that is already working on the client application which is not how any web app works. Should I also need to maintain session management at Angular level and auto sign in if my token expires on server but then it violates the principle of a dumb client or I should scrap it altogether implement sessions at NodeJS it self? Another thing is if I implement the WebTokenStrategy I found that for every request that comes from the client I'll be making a trip to database to verify a user which I can cache in session if I'm doing session management on NodeJS.
Last thing that I have a hard time figuring out is okay I can secure my resources on NodeJS but then I also need my routes and pages to be served depending on user rights in my client application, should I also store this information in the NodeJS database and serve by the same API server but I think this again violates the single responsibility principle or should there be another database for this client site route and user management.
Can someone suggest a good approach and if possible with examples?
Thanks.
No a JSON web token do not required a trip to the database since you encode the information you want on the payload. However you can implement a redis strategy if you want to be able to revoke them (For right changes for example). The signature part will be used by your server to ensure the authenticity (thanks to your server-side JWT secret).
You can also choose the expiration time you want. But if you want to limit it to 30 minutes you can also implement a renew strategy. (Ask for a new token before the old one will expire soon : The server will just deliver a new token with the same data encode. For the front end renew strategy you can use such a lib :
'use strict';
/**
* Helper class to decode and find JWT expiration.
*/
class JwtHelper {
urlBase64Decode(str) {
let output = str.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0: { break; }
case 2: { output += '=='; break; }
case 3: { output += '='; break; }
default: {
throw 'Illegal base64url string!';
}
}
return this.b64DecodeUnicode(output);
}
// credits for decoder goes to https://github.com/atk
b64decode(str) {
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let output = '';
str = String(str).replace(/=+$/, '');
if (str.length % 4 == 1) {
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (
// initialize result and counters
let bc = 0, bs, buffer, idx = 0;
// get next character
buffer = str.charAt(idx++);
// character found in table? initialize bit storage and add its ascii value;
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf(buffer);
}
return output;
}
// https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(this.b64decode(str), (c) => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
decodeToken(token) {
let parts = token.split('.');
if (parts.length !== 3) {
throw new Error('JWT must have 3 parts');
}
let decoded = this.urlBase64Decode(parts[1]);
if (!decoded) {
throw new Error('Cannot decode the token');
}
return JSON.parse(decoded);
}
getTokenExpirationDate(token) {
let decoded;
decoded = this.decodeToken(token);
if (!decoded.hasOwnProperty('exp')) {
return null;
}
let date = new Date(0); // The 0 here is the key, which sets the date to the epoch
date.setUTCSeconds(decoded.exp);
return date;
}
isTokenExpired(token, offsetSeconds) {
let date = this.getTokenExpirationDate(token);
offsetSeconds = offsetSeconds || 0;
if (date == null) {
return false;
}
// Token expired?
return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
}
}
const jwtHelper = new JwtHelper();
const decodedData = jwtHelper.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ');
console.log(decodedData)
So I am programming a timetable app in meteor, using the UNTIS backend. The problem I'm facing right now is, that I don't want each user to re-enter their passwords each time there is a request to the server. Sometimes I can't even (for example at 6 am to check if the first lesson isn't cancelled after all).
The problem is, that the password is needed in plain. So the password has to be accesible in plain at some point or another on the server.
The way I solved this problem:
I created a Meteor settings file:
/development.json
{
"ENCRYPT_PASSW_ENCRYPTION_KEY": "*some long encryption key*",
"ENCRYPT_PASSW_SALT_LENGTH": 32,
"ENCRYPT_PASSW_USER_KEY_LENGTH": 32,
"ENCRYPT_PBKDF2_ROUNDS": 100,
"ENCRYPT_PBKDF2_DIGEST": "sha512",
"ENCRYPT_PASSW_ALGORITHM": "aes-256-ctr"
}
In order to be able to use these settings in your meteor app, you will have to start meteor like this: meteor run --settings development.json
Sidenote: Of course you have to add your own parameters. These are just the development settings. You will have to choose your own parameters, depending on the importance of your data. (PBKDF2_ROUNDS should be chosen to fit your host system. I've read somewhere that a hash should take at least 241 milliseconds)
Some server side functions:
// server/lib/encryption.js
const crypto = require("crypto");
// generate a cryptograhpically secure salt
// with the length specified in the settings
generateUserSalt = function (length = Meteor.settings.ENCRYPT_PASSW_SALT_LENGTH) {
return crypto.randomBytes(length).toString("base64");
}
// encrypt a password with a key, derived from the
// application key plus the users salt
encryptUserPass = function (uid, pass, salt = false) {
const key = getUserKey(uid, salt),
algorithm = Meteor.settings.ENCRYPT_PASSW_ALGORITHM,
cipher = crypto.createCipher(algorithm, key);
return cipher.update(pass,'utf8','hex') + cipher.final('hex');
}
// decrypt a password with the same key
decryptUserPass = function (uid, ciphertext, salt = false) {
const key = getUserKey(uid, salt),
algorithm = Meteor.settings.ENCRYPT_PASSW_ALGORITHM,
decipher = crypto.createDecipher(algorithm, key);
return decipher.update(ciphertext,'hex','utf8') + decipher.final('utf8');
}
// generate the user-specific key that derives from
// the applications main encryption key plus the users
// specific salt. this is only needed in this scope
function getUserKey (uid, salt = false) {
// if no salt is given, take it from the user db
if (salt === false) {
const usr = Meteor.users.findOne(uid);
if (!usr || !usr.api_private || !usr.api_private.salt) {
throw new Meteor.Error("no-salt-given", "The salt from user with id" + uid + " couldn't be located. Maybe it's not set?");
}
salt = usr.untis_private.salt;
}
const systemKey = Meteor.settings.ENCRYPT_PASSW_ENCRYPTION_KEY,
rounds = Meteor.settings.ENCRYPT_PBKDF2_ROUNDS,
length = Meteor.settings.ENCRYPT_PASSW_USER_KEY_LENGTH,
digest = Meteor.settings.ENCRYPT_PBKDF2_DIGEST;
const userKey = crypto.pbkdf2Sync(systemKey, salt, rounds, length, digest);
return userKey.toString('hex');
}
And now I can encrypt each users password with a unique key like this:
// either with generating a salt (then the uid is not needed)
let salt = generateUserSalt(),
encPass = encryptUserPass(0, pass, salt);
// or when the user already has a salt (salt is in db)
let encPass2 = encryptUserPass(uid, pass);
Decrypting is also verry easy:
let pass = decryptUserPass(0, passEnc, salt);
// or
let pass2 = decryptUserPass(uid, passEnc);
Explanation
Of course I know, that this is still pretty bad in terms of security (storing things on the server that can be reversed into user's passwords). The reason why I think this is okay:
Each users password is encrypted like this:
AES(password, PBKDF2(global-encryption-key + salt))
This means that:
each user's password is encrypted with a different key
no encryption keys are saved in the db
Why I think this is a good sollution:
In case the database is leaked, the attacker will firstly need to guess the AES key correctly for one specific user and then reverse the PBKDF2 to find the global-encryption-key. or
Guess the global-encryption-key
Therefore you should chose a rather large global-encryption-key.
Facts about salts
NEVER use a salt twice
Change salts together with passwords (don't reuse them)
Salts should be long: rule of thumb: make the salt as long the output of the hash function (sha256 = 32 bytes)
more about salting things
I have a web app being built in express.js with a postgresql db.
I was wondering about how to implement the security, but everyone uses something different ( i guess thats a good thing? ).
Different modules different authentication sequences etc.
What I have at the moment:
1) User form post to for example /login
2) app routes to specific route
3) in route I try the following
var localconstring = "postgres://" + usr + ":" + pass + "#ip:port/db";
var client = new pg.Client(localconstring);
client.on('drain', client.end.bind(client));
client.connect(function (err, client, done) {
The database uses md5 so the pass is already protected by the db.
What should really happen?
Should I salt and hash the username and password and then save the salted/hashed credentials alongside the salt and then use the md5 of the db also?
If so which module?
Should I be logging in like that or try to do a select * from pg_roles/users ??
Thanks a lot!
(regarding the salt and hash if possible some detailed examples as I am pretty knew with authentication security)
Forgot to mention. cookies..
After the authentication I set the following cookies:
res.cookie('user', req.body.lguser.username, { signed: true })
res.cookie('watcher', o, { signed: true })
And look em up afterwards
req.signedCookies.user !== undefined
Is the signed attribute secure?
You should generate a key. This key should be saved on a cookie and on the database. Then when the user makes a petition, you can get the key on the cookie and search the user on the database.
There are libraries that help you on this, take a look at Passportjs:
http://passportjs.org/
First of all md5 is NOT seure anymore, so I would recommend you using 'sha512'.
A snippet would be something like this:
var crypto = require('crypto');
var salt = crypto.pseudoRandomBytes(32);
crypto.pbkdf2(userPassword,salt,1024,32,function(err,finalPassword){
//on the db you save the salt as a field and the SALTEDPASSWORD !!
//in this case the finalPassword
}
So when the user logs-in you get the user from the db by username and do the following:
//after getting the user from DB recalculate the hash
crypto.pbkdf2(passw,user.salt,1024,32,function(err,corrPass){
if(corrPass.toString() == user.password.toString()) // log in the user
//where user.password is the result from the db query
}
And I do recommend using passport like the other dude said, it simplifies all of the cookie stuff.
Hope it helped !
In my build process, I want to include a timestamp from an RFC-3161-compliant TSA. At run time, the code will verify this timestamp, preferably without the assistance of a third-party library. (This is a .NET application, so I have standard hash and asymmetric cryptography functionality readily at my disposal.)
RFC 3161, with its reliance on ASN.1 and X.690 and whatnot, is not simple to implement, so for now at least, I'm using Bouncy Castle to generate the TimeStampReq (request) and parse the TimeStampResp (response). I just can't quite figure out how to validate the response.
So far, I've figured out how to extract the signature itself, the public cert, the time the timestamp was created, and the message imprint digest and nonce that I sent (for build-time validation). What I can't figure out is how to put this data together to generate the data that was hashed and signed.
Here's a rough idea of what I'm doing and what I'm trying to do. This is test code, so I've taken some shortcuts. I'll have to clean a couple of things up and do them the right way once I get something that works.
Timestamp generation at build time:
// a lot of fully-qualified type names here to make sure it's clear what I'm using
static void WriteTimestampToBuild(){
var dataToTimestamp = Encoding.UTF8.GetBytes("The rain in Spain falls mainly on the plain");
var hashToTimestamp = new System.Security.Cryptography.SHA1Cng().ComputeHash(dataToTimestamp);
var nonce = GetRandomNonce();
var tsr = GetTimestamp(hashToTimestamp, nonce, "http://some.rfc3161-compliant.server");
var tst = tsr.TimeStampToken;
var tsi = tst.TimeStampInfo;
ValidateNonceAndHash(tsi, hashToTimestamp, nonce);
var cms = tst.ToCmsSignedData();
var signer =
cms.GetSignerInfos().GetSigners()
.Cast<Org.BouncyCastle.Cms.SignerInformation>().First();
// TODO: handle multiple signers?
var signature = signer.GetSignature();
var cert =
tst.GetCertificates("Collection").GetMatches(signer.SignerID)
.Cast<Org.BouncyCastle.X509.X509Certificate>().First();
// TODO: handle multiple certs (for one or multiple signers)?
ValidateCert(cert);
var timeString = tsi.TstInfo.GenTime.TimeString;
var time = tsi.GenTime; // not sure which is more useful
// TODO: Do I care about tsi.TstInfo.Accuracy or tsi.GenTimeAccuracy?
var serialNumber = tsi.SerialNumber.ToByteArray(); // do I care?
WriteToBuild(cert.GetEncoded(), signature, timeString/*or time*/, serialNumber);
// TODO: Do I need to store any more values?
}
static Org.BouncyCastle.Math.BigInteger GetRandomNonce(){
var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
var bytes = new byte[10]; // TODO: make it a random length within a range
rng.GetBytes(bytes);
return new Org.BouncyCastle.Math.BigInteger(bytes);
}
static Org.BouncyCastle.Tsp.TimeStampResponse GetTimestamp(byte[] hash, Org.BouncyCastle.Math.BigInteger nonce, string url){
var reqgen = new Org.BouncyCastle.Tsp.TimeStampRequestGenerator();
reqgen.SetCertReq(true);
var tsrequest = reqgen.Generate(Org.BouncyCastle.Tsp.TspAlgorithms.Sha1, hash, nonce);
var data = tsrequest.GetEncoded();
var webreq = WebRequest.CreateHttp(url);
webreq.Method = "POST";
webreq.ContentType = "application/timestamp-query";
webreq.ContentLength = data.Length;
using(var reqStream = webreq.GetRequestStream())
reqStream.Write(data, 0, data.Length);
using(var respStream = webreq.GetResponse().GetResponseStream())
return new Org.BouncyCastle.Tsp.TimeStampResponse(respStream);
}
static void ValidateNonceAndHash(Org.BouncyCastle.Tsp.TimeStampTokenInfo tsi, byte[] hashToTimestamp, Org.BouncyCastle.Math.BigInteger nonce){
if(tsi.Nonce != nonce)
throw new Exception("Nonce doesn't match. Man-in-the-middle attack?");
var messageImprintDigest = tsi.GetMessageImprintDigest();
var hashMismatch =
messageImprintDigest.Length != hashToTimestamp.Length ||
Enumerable.Range(0, messageImprintDigest.Length).Any(i=>
messageImprintDigest[i] != hashToTimestamp[i]
);
if(hashMismatch)
throw new Exception("Message imprint doesn't match. Man-in-the-middle attack?");
}
static void ValidateCert(Org.BouncyCastle.X509.X509Certificate cert){
// not shown, but basic X509Chain validation; throw exception on failure
// TODO: Validate certificate subject and policy
}
static void WriteToBuild(byte[] cert, byte[] signature, string time/*or DateTime time*/, byte[] serialNumber){
// not shown
}
Timestamp verification at run time (client site):
// a lot of fully-qualified type names here to make sure it's clear what I'm using
static void VerifyTimestamp(){
var timestampedData = Encoding.UTF8.GetBytes("The rain in Spain falls mainly on the plain");
var timestampedHash = new System.Security.Cryptography.SHA1Cng().ComputeHash(timestampedData);
byte[] certContents;
byte[] signature;
string time; // or DateTime time
byte[] serialNumber;
GetDataStoredDuringBuild(out certContents, out signature, out time, out serialNumber);
var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(certContents);
ValidateCert(cert);
var signedData = MagicallyCombineThisStuff(timestampedHash, time, serialNumber);
// TODO: What other stuff do I need to magically combine?
VerifySignature(signedData, signature, cert);
// not shown: Use time from timestamp to validate cert for other signed data
}
static void GetDataStoredDuringBuild(out byte[] certContents, out byte[] signature, out string/*or DateTime*/ time, out byte[] serialNumber){
// not shown
}
static void ValidateCert(System.Security.Cryptography.X509Certificates.X509Certificate2 cert){
// not shown, but basic X509Chain validation; throw exception on failure
}
static byte[] MagicallyCombineThisStuff(byte[] timestampedhash, string/*or DateTime*/ time, byte[] serialNumber){
// HELP!
}
static void VerifySignature(byte[] signedData, byte[] signature, System.Security.Cryptography.X509Certificates.X509Certificate2 cert){
var key = (RSACryptoServiceProvider)cert.PublicKey.Key;
// TODO: Handle DSA keys, too
var okay = key.VerifyData(signedData, CryptoConfig.MapNameToOID("SHA1"), signature);
// TODO: Make sure to use the same hash algorithm as the TSA
if(!okay)
throw new Exception("Timestamp doesn't match! Don't trust this!");
}
As you might guess, where I think I'm stuck is the MagicallyCombineThisStuff function.
I finally figured it out myself. It should come as no surprise, but the answer is nauseatingly complex and indirect.
The missing pieces to the puzzle were in RFC 5652. I didn't really understand the TimeStampResp structure until I read (well, skimmed through) that document.
Let me describe in brief the TimeStampReq and TimeStampResp structures. The interesting fields of the request are:
a "message imprint", which is the hash of the data to be timestamped
the OID of the hash algorithm used to create the message imprint
an optional "nonce", which is a client-chosen identifier used to verify that the response is generated specifically for this request. This is effectively just a salt, used to avoid replay attacks and to detect errors.
The meat of the response is a CMS SignedData structure. Among the fields in this structure are:
the certificate(s) used to sign the response
an EncapsulatedContentInfo member containing a TSTInfo structure. This structure, importantly, contains:
the message imprint that was sent in the request
the nonce that was sent in the request
the time certified by the TSA
a set of SignerInfo structures, with typically just one structure in the set. For each SignerInfo, the interesting fields within the structure are:
a sequence of "signed attributes". The DER-encoded BLOB of this sequence is what is actually signed. Among these attributes are:
the time certified by the TSA (again)
a hash of the DER-encoded BLOB of the TSTInfo structure
an issuer and serial number or subject key identifier that identifies the signer's certificate from the set of certificates found in the SignedData structure
the signature itself
The basic process of validating the timestamp is as follows:
Read the data that was timestamped, and recompute the message imprint using the same hashing algorithm used in the timestamp request.
Read the nonce used in the timestamp request, which must be stored along with the timestamp for this purpose.
Read and parse the TimeStampResp structure.
Verify that the TSTInfo structure contains the correct message imprint and nonce.
From the TimeStampResp, read the certificate(s).
For each SignerInfo:
Find the certificate for that signer (there should be exactly one).
Verify the certificate.
Using that certificate, verify the signer's signature.
Verify that the signed attributes contain the correct hash of the TSTInfo structure
If everything is okay, then we know that all signed attributes are valid, since they're signed, and since those attributes contain a hash of the TSTInfo structure, then we know that's okay, too. We have therefore validated that the timestamped data is unchanged since the time given by the TSA.
Because the signed data is a DER-encoded BLOB (which contains a hash of the different DER-encoded BLOB containing the information the verifier actually cares about), there's no getting around having some sort of library on the client (verifier) that understands X.690 encoding and ASN.1 types. Therefore, I conceded to including Bouncy Castle in the client as well as in the build process, since there's no way I have time to implement those standards myself.
My code to add and verify timestamps is similar to the following:
Timestamp generation at build time:
// a lot of fully-qualified type names here to make sure it's clear what I'm using
static void WriteTimestampToBuild(){
var dataToTimestamp = ... // see OP
var hashToTimestamp = ... // see OP
var nonce = ... // see OP
var tsq = GetTimestampRequest(hashToTimestamp, nonce);
var tsr = GetTimestampResponse(tsq, "http://some.rfc3161-compliant.server");
ValidateTimestamp(tsq, tsr);
WriteToBuild("tsq-hashalg", Encoding.UTF8.GetBytes("SHA1"));
WriteToBuild("nonce", nonce.ToByteArray());
WriteToBuild("timestamp", tsr.GetEncoded());
}
static Org.BouncyCastle.Tsp.TimeStampRequest GetTimestampRequest(byte[] hash, Org.BouncyCastle.Math.BigInteger nonce){
var reqgen = new TimeStampRequestGenerator();
reqgen.SetCertReq(true);
return reqgen.Generate(TspAlgorithms.Sha1/*assumption*/, hash, nonce);
}
static void GetTimestampResponse(Org.BouncyCastle.Tsp.TimeStampRequest tsq, string url){
// similar to OP
}
static void ValidateTimestamp(Org.BouncyCastle.Tsp.TimeStampRequest tsq, Org.BouncyCastle.Tsp.TimeStampResponse tsr){
// same as client code, see below
}
static void WriteToBuild(string key, byte[] value){
// not shown
}
Timestamp verification at run time (client site):
/* Just like in the OP, I've used fully-qualified names here to avoid confusion.
* In my real code, I'm not doing that, for readability's sake.
*/
static DateTime GetTimestamp(){
var timestampedData = ReadFromBuild("timestamped-data");
var hashAlg = Encoding.UTF8.GetString(ReadFromBuild("tsq-hashalg"));
var timestampedHash = System.Security.Cryptography.HashAlgorithm.Create(hashAlg).ComputeHash(timestampedData);
var nonce = new Org.BouncyCastle.Math.BigInteger(ReadFromBuild("nonce"));
var tsq = new Org.BouncyCastle.Tsp.TimeStampRequestGenerator().Generate(System.Security.Cryptography.CryptoConfig.MapNameToOID(hashAlg), timestampedHash, nonce);
var tsr = new Org.BouncyCastle.Tsp.TimeStampResponse(ReadFromBuild("timestamp"));
ValidateTimestamp(tsq, tsr);
// if we got here, the timestamp is okay, so we can trust the time it alleges
return tsr.TimeStampToken.TimeStampInfo.GenTime;
}
static void ValidateTimestamp(Org.BouncyCastle.Tsp.TimeStampRequest tsq, Org.BouncyCastle.Tsp.TimeStampResponse tsr){
/* This compares the nonce and message imprint and whatnot in the TSTInfo.
* It throws an exception if they don't match. This doesn't validate the
* certs or signatures, though. We still have to do that in order to trust
* this data.
*/
tsr.Validate(tsq);
var tst = tsr.TimeStampToken;
var timestamp = tst.TimeStampInfo.GenTime;
var signers = tst.ToCmsSignedData().GetSignerInfos().GetSigners().Cast<Org.BouncyCastle.Cms.SignerInformation>();
var certs = tst.GetCertificates("Collection");
foreach(var signer in signers){
var signerCerts = certs.GetMatches(signer.SignerID).Cast<Org.BouncyCastle.X509.X509Certificate>().ToList();
if(signerCerts.Count != 1)
throw new Exception("Expected exactly one certificate for each signer in the timestamp");
if(!signerCerts[0].IsValid(timestamp)){
/* IsValid only checks whether the given time is within the certificate's
* validity period. It doesn't verify that it's a valid certificate or
* that it hasn't been revoked. It would probably be better to do that
* kind of thing, just like I'm doing for the signing certificate itself.
* What's more, I'm not sure it's a good idea to trust the timestamp given
* by the TSA to verify the validity of the TSA's certificate. If the
* TSA's certificate is compromised, then an unauthorized third party could
* generate a TimeStampResp with any timestamp they wanted. But this is a
* chicken-and-egg scenario that my brain is now too tired to keep thinking
* about.
*/
throw new Exception("The timestamp authority's certificate is expired or not yet valid.");
}
if(!signer.Verify(signerCerts[0])){ // might throw an exception, might not ... depends on what's wrong
/* I'm pretty sure that signer.Verify verifies the signature and that the
* signed attributes contains a hash of the TSTInfo. It also does some
* stuff that I didn't identify in my list above.
* Some verification errors cause it to throw an exception, some just
* cause it to return false. If it throws an exception, that's great,
* because that's what I'm counting on. If it returns false, let's
* throw an exception of our own.
*/
throw new Exception("Invalid signature");
}
}
}
static byte[] ReadFromBuild(string key){
// not shown
}
I am not sure to understand why you want to rebuild the data structure signed in the response. Actually if you want to extract the signed data from the time-stamp server response you can do this:
var tsr = GetTimestamp(hashToTimestamp, nonce, "http://some.rfc3161-compliant.server");
var tst = tsr.TimeStampToken;
var tsi = tst.TimeStampInfo;
var signature = // Get the signature
var certificate = // Get the signer certificate
var signedData = tsi.GetEncoded(); // Similar to tsi.TstInfo.GetEncoded();
VerifySignature(signedData, signature, certificate)
If you want to rebuild the data structure, you need to create a new Org.BouncyCastle.Asn1.Tsp.TstInfo instance (tsi.TstInfo is a Org.BouncyCastle.Asn1.Tsp.TstInfo object) with all elements contained in the response.
In RFC 3161 the signed data structure is defined as this ASN.1 sequence:
TSTInfo ::= SEQUENCE {
version INTEGER { v1(1) },
policy TSAPolicyId,
messageImprint MessageImprint,
-- MUST have the same value as the similar field in
-- TimeStampReq
serialNumber INTEGER,
-- Time-Stamping users MUST be ready to accommodate integers
-- up to 160 bits.
genTime GeneralizedTime,
accuracy Accuracy OPTIONAL,
ordering BOOLEAN DEFAULT FALSE,
nonce INTEGER OPTIONAL,
-- MUST be present if the similar field was present
-- in TimeStampReq. In that case it MUST have the same value.
tsa [0] GeneralName OPTIONAL,
extensions [1] IMPLICIT Extensions OPTIONAL }
Congratulations on getting that tricky protocol work done!
See also a Python client implementation at rfc3161ng 2.0.4.
Note that with the RFC 3161 TSP protocol, as discussed at Web Science and Digital Libraries Research Group: 2017-04-20: Trusted Timestamping of Mementos and other publications, you and your relying parties must trust that the Time-Stamping Authority (TSA) is operated properly and securely. It is of course very difficult, if not impossible, to really secure online servers like those run by most TSAs.
As also discussed in that paper, with comparisons to TSP, now that the world has a variety of public blockchains in which trust is distributed and (sometimes) carefully monitored, there are new trusted timestamping options (providing "proof of existence" for documents). For example see
OriginStamp - Trusted Timestamping with Bitcoin. The protocol is much much simpler, and they provide client code for a large variety of languages. While their online server could also be compromised, the client can check whether their hashes were properly embedded in the Bitcoin blockchain and thus bypass the need to trust the OriginStamp service itself.
One downside is that timestamps are only posted once a day, unless an extra payment is made. Bitcoin transactions have become rather expensive, so the service is looking at supporting other blockchains also to drive costs back down and make it cheaper to get more timely postings.
Update: check out Stellar and Keybase
For free, efficient, lightning-fast, widely-vetted timestamps, check out the Stellar blockchain protocol, and the STELLARAPI.IO service.
I would like to implement a forgotten password scenario in an web application. The system will send out an email to the user containing a unique url that the user can hit to allow them to reset their password. There is loads of guidance on this online. The following is a good linke suggesting how to implement this. Best way of doing code for "Forgotten Password"
The part I do not understand fully is the token generation. What does this mean?? Is this just a guid (or random string) that is stored on the server against the user (maybe in the users db table). The guid is also sent in the url (as querystring) so that when the request hits the web server it can look the guid up and find which user account to reset. Is there more to it than this? Many people talk about token expiration. I could store an expiration time against the guid after which the account reset cannot be done.
Some have suggested a CSRF token, but I cannot understand how this would work in this scenario.
Any guidance would be much appreciated... :)
Storing a randomly generated token of (at least) 128 bits server-side, together with the username and an expiration date, will work perfectly fine.
Another way to achieve the same (without having to store anything server-side) is computing
hash = hash(secret + user name + expiration date)
where + denotes concenation, hash() is a cryptographically secure hash function (like SHA2) and secret is a string of (at least) 128 bits that is only known to you, and send this to the user:
user name + expiration date + hash
Both method achieve the same security, but note that - until the token expires - the user could change his password several times.
In the first case, make sure that token is created randomly (e.g. using /dev/random if you're on linux). The same goes for secret in the second. But secret is static (not newly generated for every request).
I used this piece of code to generate my token :
/**
* generates a random token, uses base64: 0-9a-zA-Z/+
* #param int [optional] $length length of token, default 24 (144 Bit)
* #return string token
*/
function generateToken($length = 24) {
if(function_exists('openssl_random_pseudo_bytes')) {
$token = base64_encode(openssl_random_pseudo_bytes($length, $strong));
if($strong == TRUE)
return substr($token, 0, $length); //base64 is about 33% longer, so we need to truncate the result
}
//fallback to mt_rand if php < 5.3 or no openssl available
$characters = '0123456789';
$characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+';
$charactersLength = strlen($characters)-1;
$token = '';
//select some random characters
for ($i = 0; $i < $length; $i++) {
$token .= $characters[mt_rand(0, $charactersLength)];
}
return $token;
}
Source : http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php#96812