FTN8: FutoIn Security Concept Version: 0.4DV Date: 2018-05-04 Copyright: 2014-2018 FutoIn Project (http://futoin.org) Authors: Andrey Galkin
Security concept is required to build a unified authentication and authorization model across security domains and use cases with separate control in open environment like internet.
There is no global trusted party is allowed. Chain of trust must be fully distributed.
External parties to particular system must never hold internal security-related data even if it is encrypted and/or signed to prevent theoretical falsification attacks in the future.
Notes:
Any authentication and authorization functionality should be accounted for failures per exercised security resource (e.g. user password, session ID, signing key, etc.). System must forcibly disable such resource at reasonable failure count threshold to prevent the attack succeeding.
As such behavior allows easy Denial-of-Service for target resource, System must block attackers earlier than it blocks the target. Also, target identifiers must not be easily guessable (e.g. avoid sequential or derived identifiers).
All thresholds must be specific to time period like day, week, month, quarter, year.
Notes:
Each unsuccessful attempt to exercise security operation must be accounted per client source address and other meaningful identifiers. Such accounting should be also aggregated based on network range and/or domain to protect from distributed attacks.
However, such behavior can harm legit users. Therefore, complete blocking should be the last resort. Instead, System should take more light measures like throttling requests per minute and preliminary verification if session is run by real human. Such light measures are mandatory, if System is aware of legit user.
Notes:
Online verification must always be enforced even if it complicates processing, imposes scalability issues and/or increases latency. FutoIn Security Concept is strictly against any token-based authentication where not trusted party like Client is part of communication channel between Service and AuthService.
Caching is essential to achieve performance and scalability similar to pure token-based approaches. However, such caching is allowed only if reliable cache invalidation is implemented.
All fully compliant Services must use AuthService for security meaningful decisions.
Such AuthService must allow a complete overview and control of granted accesses in the given Service or on behalf of the User.
All secrets used in automated contexts should be rotated based on both usage time and usage count thresholds. This is a simple measures for possible leak of secrets.
Identification of secrets should also be rotated based on UUID v4 to complicate tracing of actors.
User-defined secrets like password, historical One-Time-Password solution and similar secrets are not required to be rotated.
Some "not important" characteristics may be used to plan attacks and/or to extract business secrets.
General rules of well-known cryptography practices apply here:
There are three major security contexts:
Each Service and each Client must trust only one AuthService.
The standard authorization mechanism does not allow Client code to access protected resources in another Service directly. Such feature may be implemented as sub-spec.
In addition, the following optional services exist:
It may look as too much overhead for a single request processing, but any decent system does exactly the same in fact: security processing is centralized in some application module.
FutoIn converts modules to (micro-)services by fundamental design. There should be efficient in-process calling mechanism to minimize penalties.
It is known that symmetric cryptography is much faster to process than asymmetric. Therefore, message signing is based on automatically rotated shared symmetric secrets which unique to each pair of peers. However, asymmetric cryptography is used for key rotation to ensure forward secrecy.
Client Service AuthService DefenseSystem
| . . .
|-- request ->| . .
. |----------- onCall() --------->|
. . . [may fail early]
. |<------ defense action --------|
. |-- checkAuth() -->| .
. |<-- auth rsp -----| .
. |- checkAccess() ->| .
. . [fail on error] .
. [defense action] . .
. [process] . .
. |----------- onResult() ------->|
|<- response -| . .
| . . .
It's assumed that one of Message Authentication Code approaches used requires a preshared secret and secure updates of that.
Initial manual registration:
Operator AuthService
| .
|--- register Service -->|
. [gen initial secret]
|<-- clear text secret --|
[manually configures Service] .
. .
Initial automatic registration through secure channel, if allowed:
Service AuthService
| .
[gen temporary assymetric key] .
|----- request registration ------->|
. [depend on transport's MitM security]
. [save request for approval]
|<----- provide ticket ID ----------|
. .
[put ticket ID in .well-known] |
. .
|-- try to complete registration -->|
. [ticket is not validated]
|<-- HTTPS GET .well-known ticket --|
|------- return ticket ID --------->|
. [ticket is validated]
|<- "pending" or "rejected" error --|
. .
[reasonable delay] [wait approval]
. .
|-- try to complete registration -->|
|<- "pending" or "rejected" error --|
. .
[reasonable delay] [operator/auto approval]
. .
|-- try to complete registration -->|
. [generate new secret]
|<-- return new encrypted secret ---|
[decrypt secret and discard key] .
[use new secret] .
| .
Shared secret secure exchange:
Service AuthService
| .
[gen temporary assymetric key] .
|----- request new secret --------->|
. [use current secret for MitM security]
. [generate new secret]
|<-- return new encrypted secret ---|
[decrypt secret and discard key] .
[use new secret] .
| .
General goal is to concentrate user authentication and access grants in single place - AuthService. See below for description of Access Request Templates.
Register authorization access request templates:
Service AuthService
| .
|-------- create template --------->|
. [register template of required access requests]
|<----- return redirect details ----|
[use secret to sign & check redirects on both peers]
| .
First visit of Service ever:
Client Service AuthService
| . .
|------ visit ------>| .
. [create signed redirect from template]
|<---- redirect -----| .
|----- provide signed payload --------------->|
. . [verify sig]
. . [drive user registration/login process]
. . [ask user for access]
|<---- redirect user back --------------------|
|-- signed result -->| .
. [verify sig] .
. |-- register session --->|
. |<-------- OK -----------|
|<--- logged in -----| .
[use of Service] . .
. [periodic renew] .
. |--- renew session ----->|
. |<-------- OK -----------|
[use of Service] . .
|---- logout ------->| .
. |----- end session ----->|
. |<-------- OK -----------|
|<--- logged out ----| .
| . .
Second visit of Service from known device:
Client Service AuthService
| . .
|---- visit --->| .
. [check known user] .
. |--- renew session ----->|
. |<-------- OK -----------|
|<- logged in --| .
[use of Service] . .
| . .
Immediate logout:
Service AuthService
| .
|---- listen for events -->|
. .
. .
|<--- invalidate session --|
[invalidate local session] .
. .
Foreign users (local AuthService acts as proxy):
Client Service AuthService ForeignAuthService
. . . .
. . |---- register self -->|
. . . .
. |-- listen for events -->| .
. . |- listen for events ->|
|---- visit --->| . .
. [unknown user] . .
|<-- redirect --| . .
|--- provide signed payload ------------>| .
. . [verify sig] .
. . [user chooses extral auth] .
|<----- redirect to foreign -------------| .
|-------------------------- provide signed payload ------------>|
. . . [verify sig]
. . . [process user auth]
|<--------- return back local AuthService ----------------------|
|--- provide signed return ------------->| .
. . [verify sig] .
. . |-- register session ->|
. . |<------ OK -----------|
|<-- return back Service ----------------| .
|-- get back -->| . .
. [verify sig] . .
. |-- register session --->| .
. |<-------- OK -----------| .
|<- logged in --| . .
[use of Service] . . .
| . . .
On-behalf-of calls is a standard feature of FTN3.
User is a generic term for security subject. It can be a human, a specific service, a group or even some object.
Each Service registers a list of generic access descriptors it provides. Those can be granted by User to another User or Service.
Another Service creates Access Request Templates as a list of generic access descriptors it wants to ask from User. When User grants the required access, Service can call another Service on behalf of the User.
It's assumed that user has full access to own resources protected only by required security levels. User can grant resource access to another User (Service) based on Access Control descriptors.
Local user:
Client Service1 Service2 AuthService
. . . .
. . |- register descriptors ->|
. . . .
. |-- create template ------------------->|
. . . .
|-- visit --->| . .
|<- redirect -| . .
|----- provide signed payload ----------------------->|
. . . [verify sig]
. . . [ask user]
. . . [grant access]
|<----- signed redirect back -------------------------|
|- return --->| . .
. [verify sig] . .
. |- API call ->| .
. . [request checking] .
. . |---- checkAuth() ------->|
. . |--- checkAccess() ------>|
. . [request processing] .
. |<-- result --| .
. . . .
Foreign user access just adds extra complexity:
In many cases Service needs to securely confirm some action like bank transfer approval. For that reason, Service creates special confirmation request in AuthService and redirects the user there.
Client Service AuthService
| . .
|- request action ->| .
. |- prepare confirmation ->|
. . [store with timeout]
. |<-- provide URL ---------|
|<---- redirect ----| .
|------ use the AuthService URL ------------->|
. . [ask user confirmation]
. . [store result]
|<---- signed redirect back ------------------|
|-- return -------->| .
. [verify sig] .
. |-- verify confirmation ->|
. |<----- OK ---------------|
. [complete action] .
. . .
The scope of AuthService is arbitrary - it is formed by AuthService itself. However, AuthService should have a single domain name which is used as global scope identifier.
Each user has a unique local ID and global ID.
Local ID is arbitrary and assigned by AuthService. Base64-coded UUID without padding is suggested.
Global ID is based on local ID and scope name of home AuthService. Typically, email address is the global identifier for users and fully qualified domain name is the global identifier for services.
If the Service or Client trust different AuthServices then it is responsibility of AuthService to establish communication to another AuthService. In such case, Client is called foreign AuthService user or simply foreign user.
Foreign users are detected based on mismatch of associated global ID scope name and current AuthService scope name.
Theoretically, AuthService can auto-discover and establish registration to any other foreign AuthService. However, it may be undesired from security point of view. So, only whitelisted foreign AuthServices should be allowed. Whitelist can be either local or global in form of association of AuthService providers.
Each Service as logical entity is assumed to have own user ID in scope of the related AuthService. Therefore, communication authentication follows the same pattern as Client-to-Service pattern.
Client-to-Service and Service-to-Service communication has different natural aspects:
Therefore, specification is separated for Client(human) and Service(software) cases.
There are many OpenID, OAuth, SAML and other single sign-on alternatives. Particular AuthService may easily integrate with those.
It's assumed that each Service is accompanied by unified AuthService logic to efficiently process requests on scale. Such AuthService can be either full-featured or limited to support of only foreign users.
MAC stays for Message Authentication Code which helps to ensure message integrity.
Note 1: MAC logic must be abstract of JSON as far as possible to be efficiently used in other message coding methods.
Note 2: research to be done to support TupleHash and non-JSON representation of fields as an option.
Note 3: for performance and simplification a special FutoIn interface to be created for message-in-signed-message-field approach.
Executor may refuse to support any MAC algo and throw SecurityError.
HMD5
- HMAC MD5 128-bit (acceptably secure, even though MD5 itself is weak)HS256
- SHA v2 256-bit (acceptably secure)HS384
- SHA v2 384-bit (acceptably secure)HS512
- SHA v2 512-bit (acceptably secure)KMAC128
- Keccak MAC 128-bit (high secure at the moment)KMAC256
- Keccak MAC 256-bit (high secure at the moment)Note: current suggested default is HS256
If request comes signed with any MAC then response must also be signed with MAC using exactly the same secret key and MAC algorithm.
Invoker must validate response "sec" field and fail on mismatch or absence of one.
It is called "Strategy", but not "Function" on purpose as the same KDF may be used different ways.
Derived Key ID must be transmitted as Base64 encoding string without padding. Key ID or salt should be of recommended size, if applicable.
Based on this strategy, no key ID or a fixed minimal derived key ID may be used for current version of the specification to minimize performance impact and simplify Executor's derived key caching logic. Master Secret itself should provide enough entropy to ensure derived key's quality. So, key update gets bound to frequency of Master Secret update. Key derivation would be used only to get different keys based on its purpose.
Below is a list of ASCII values to use for altering key derivation logic.
MAC
- for message signing.ENC
- for general encryption.EXPOSED
- for signature generating which definitely goes through untrusted exposed
channel (e.g. user's web browser).In most cases, it's not feasible to generate a new derived key for every message, so Invoker should be able to reuse the key at own discretion.
As a defensive measure, Executor peer is allowed to reject requests, if derived key either changes too often or used for too long. Executor should cache derived keys for reasonable time, but still prevent their leaking outside. Executor should consider that Invoker may be clustered with unique derived key at every node.
Executor can be configured to support only certain types of strategies named below and to reject requests with "SecurityError" on mismatch.
HKDF256
- HKDF with SHA-256HKDF512
- HKDF with SHA-512[HKDF][] is well-known modern method which has the following parameters: hash function, salt and info.
salt
as {global user ID}:{purpose}
, where global user id is the Executor peer.prm
is passed to info
parameter.MAC
and EXPOSED
case, use of current ISO
date time in YYYYMMDD
or more precise format is suggested.ENC
case, use of UUID per encryption is suggested.It's important to understand characteristics of performed user authentication in many cases.
Anonymous
- placeholder for not authenticated userInfo
- read-only access to private informationSafeOps
- Info + access to operation, which should not seriously compromise the systemPrivilegedOps
- SafeOps + access to operations, which may compromise the system. Requires SecureChannelExceptionalOps
- PrivilegedOps + access to very sensitive operations, like password changeSystem
- internal calls inside the same Service (can be cross-process)The following fingerpints are essential to enforce extra level of protection.
user_agent
- refers to HTTP 'User-Agent' or similarsource_ip
- refers to IPv4/IPv6 source address of Clientx509
- X.509 certificate, if provided by clientssh_pubkey
- SSH public key, if provided by clientclient_token
- Service-specific identification of the Client devicemisc
- implementation-defined mapflavour
- implementation typeThe specification suggests the following limits to be enforced. Hit of the limits must block any processing. The blocking must be done for entire period of enforcement.
Advanced System should have more light protection measures first to protect legit user access.
USR_NEW
- new user is createdlocal_id
- local user IDUSR_MOD
- user info updatedlocal_id
- local user ID{
"iface" : "futoin.auth.types",
"version" : "{ver}",
"ftn3rev" : "1.9",
"imports" : [
"futoin.types:1.0"
],
"types" : {
"LocalUserID" : "UUIDB64",
"LocalUser" : {
"type" : "string",
"regex" : "^[a-zA-Z]([a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9])?$"
},
"LocalService" : "LocalUser",
"GlobalService" : {
"type" : "Domain",
"maxlen" : 128
},
"GlobalUser" : {
"type" : "Email",
"maxlen" : 128
},
"GlobalUserID" : [ "GlobalUser", "GlobalService" ],
"DomainList" : {
"type" : "array",
"elemtype" : "Domain",
"minlen" : 1
},
"MACAlgo" : {
"type" : "enum",
"items" : [
"HMD5",
"HS256",
"HS384",
"HS512",
"KMAC128",
"KMAC256"
]
},
"Password" : {
"type" : "string",
"minlen" : 8,
"maxlen" : 32
},
"PasswordLength" : {
"type" : "integer",
"min" : 8,
"max" : 32
},
"KeyBits" : {
"type" : "enum",
"items" : [256, 512]
},
"MACKey" : {
"type": "Base64",
"minlen" : 42,
"maxlen" : 87
},
"StatelessSecret": [ "Password", "MACKey" ],
"MACValue" : {
"type" : "Base64",
"minlen" : 1,
"maxlen" : 128
},
"MACBase" : {
"type" : "data",
"minlen" : 8
},
"MasterSecretID" : "UUIDB64",
"MasterScope" : "Domain",
"KeyDerivationStrategy" : {
"type" : "enum",
"items" : [
"HKDF256",
"HKDF512"
]
},
"KeyPurpose" : {
"type" : "enum",
"items" : [
"MAC",
"ENC",
"EXPOSED"
]
},
"ExchangeKeyType" : {
"type" : "enum",
"items" : [
"RSA",
"X25519",
"X448"
]
},
"ExchangeKey" : {
"type" : "Base64",
"minlen" : 1,
"maxlen" : 20000
},
"EncryptedKey" : {
"type" : "Base64",
"minlen" : 1,
"maxlen" : 1000
},
"EncryptedMasterSecret" : "EncryptedKey",
"UserAgent" : {
"type" : "string",
"maxlen" : 256
},
"X509Cert" : {
"type" : "Base64",
"maxlen" : 20000
},
"SSHPubKey" : {
"type" : "string",
"maxlen" : 1000
},
"ClientToken" : {
"type" : "Base64",
"maxlen" : 342,
"desc" : "Unique per Service per Client device persistent token"
},
"ClientFingerprints" : {
"type" : "map",
"fields" : {
"user_agent" : {
"type" : "UserAgent",
"optional" : true
},
"source_ip" : {
"type" : "IPAddress",
"optional" : true
},
"x509" : {
"type" : "X509Cert",
"optional" : true
},
"ssh_pubkey" : {
"type" : "SSHPubKey",
"optional" : true
},
"client_token" : {
"type" : "ClientToken",
"optional" : true
},
"misc" : {
"type" : "map",
"optional" : true
}
}
},
"AuthInfo" : {
"type" : "map",
"fields" : {
"local_id" : "LocalUserID",
"global_id" : "GlobalUserID"
}
},
"RedirectURL" : {
"type" : "string",
"regex" : "^https?://[a-z0-9-]+(\\.[a-z0-9-]+)*\\.[a-z]{2,}/[a-zA-Z0-9_/-]*(\\?[a-zA-Z][a-zA-Z0-9]*=)?$",
"maxlen" : 128
},
"ResourceURL" : {
"type" : "string",
"regex" : "^https?://[a-z0-9-]+(\\.[a-z0-9-]+)*\\.[a-z]{2,}/[a-zA-Z0-9_/?=%&;.-]*$",
"maxlen" : 128
},
"ParamConstraint" : {
"type" : "map",
"elemtype" : "array"
},
"AccessControlDescriptor" : {
"type" : "map",
"fields" : {
"iface" : {
"type" : "FTNFace",
"optional" : true
},
"ver" : {
"type" : "FTNVersion",
"optional" : true
},
"func" : {
"type" : "FTNFunction",
"optional" : true
},
"params" : {
"type" : "ParamConstraint",
"optional" : true,
"desc": "Named paramater must match one of the values"
}
},
"desc" : "Granted API access constraints in scope of arbitrary Service"
},
"AccessControlDescriptorList" : {
"type" : "array",
"elemtype" : "AccessControlDescriptor",
"desc" : "List of granted API access in scope of arbitrary Service"
},
"AccessGroupName" : {
"type" : "GenericIdentifier",
"maxlen" : 32,
"desc" : "Service-specific arbitrary ACD grouping identifier"
},
"AccessGroup" : {
"type" : "map",
"fields" : {
"id" : "AccessGroupName",
"name" : "ItemTranslations",
"desc" : "ItemTranslations",
"acds" : "AccessControlDescriptorList",
"icon" : "ResourceURL"
},
"desc" : "Services-specific arbitrary ACD grouping definition"
},
"AccessGroupList" : {
"type" : "array",
"elemtype" : "ServiceAccessGroup",
"desc" : "List of Service-specific ACD groupings"
},
"ServiceAccessGroup" : {
"type" : "map",
"fields" : {
"service" : "GlobalService",
"access_group" : "AccessGroupName"
},
"desc" : "Global pointer to ACD group of specific Service"
},
"ServiceAccessGroupList" : {
"type" : "array",
"elemtype" : "ServiceAccessGroup",
"desc" : "List of global pointers to Service-specific ACD groups"
}
}
}
{
"iface" : "futoin.auth.manage",
"version" : "{ver}",
"ftn3rev" : "1.9",
"imports" : [
"futoin.ping:1.0",
"futoin.auth.types:{ver}"
],
"funcs" : {
"setup" : {
"params" : {
"domains" : "DomainList",
"clear_auth" : {
"type" : "boolean",
"default" : null
},
"mac_auth" : {
"type" : "boolean",
"default" : null
},
"master_auth" : {
"type" : "boolean",
"default" : null
},
"master_auto_reg" : {
"type" : "boolean",
"default" : null
},
"auth_service" : {
"type" : "boolean",
"default" : null
},
"password_len" : {
"type" : "PasswordLength",
"default" : null
},
"key_bits" : {
"type" : "KeyBits",
"default" : null
},
"def_user_ms_max" : {
"type" : "NotNegativeInteger",
"default" : null
},
"def_service_ms_max" : {
"type" : "NotNegativeInteger",
"default" : null
}
},
"result" : "boolean",
"seclvl" : "System"
},
"genConfig" : {
"result" : {
"domains" : "DomainList",
"clear_auth" : "boolean",
"mac_auth" : "boolean",
"master_auth" : "boolean",
"master_auto_reg" : "boolean",
"auth_service" : "boolean",
"password_len" : "PasswordLength",
"key_bits" : "KeyBits",
"def_user_ms_max" : "NotNegativeInteger",
"def_service_ms_max" : "NotNegativeInteger"
},
"seclvl" : "System"
},
"ensureUser" : {
"params" : {
"user" : "LocalUser",
"domain" : "GlobalService"
},
"result" : "LocalUserID",
"seclvl" : "System"
},
"ensureService" : {
"params" : {
"hostname" : "LocalService",
"domain" : "GlobalService"
},
"result" : "LocalUserID",
"seclvl" : "System"
},
"getUserInfo" : {
"params" : {
"local_id" : "LocalUserID"
},
"result" : {
"local_id" : "LocalUserID",
"global_id" : "GlobalUserID",
"is_local" : "boolean",
"is_enabled" : "boolean",
"is_service" : "boolean",
"ms_max" : "NotNegativeInteger",
"ds_max" : "NotNegativeInteger",
"created" : "Timestamp",
"updated" : "Timestamp"
},
"throws" : [
"UnknownUser"
],
"seclvl" : "System"
},
"setUserInfo" : {
"params" : {
"local_id" : "LocalUserID",
"is_enabled" : {
"type": "boolean",
"default": null
},
"ms_max" : {
"type": "NotNegativeInteger",
"default": null
},
"ds_max" : {
"type": "NotNegativeInteger",
"default": null
}
},
"result" : "boolean",
"throws" : [
"UnknownUser"
],
"seclvl" : "System"
}
},
"requires" : [
"SecureChannel",
"MessageSignature"
]
}
=END OF SPEC=