Class MFAUser
ALLOWED_DISCREPANCY = 1), i.e. a 90 second window for the default period.Enrolment: generateSecret() then generateQRCodeData(ErrorCorrectionLevel). Sign in: validateCode(String). The digits, period and algorithm must be configured BEFORE the secret is generated; the authenticator app is enrolled with them and they cannot change afterwards.
The time source is the system clock. The server host is expected to be NTP-disciplined at operating system level.
Since SignIn L-6 an instance is also made from a secret that was saved (MFAUser(String,String,String,Hashing,int,int)), and a validation tells the time step of the code it accepted (validateCodeStep(String)), which is what a caller needs to refuse a code that was used before. This class keeps nothing: the sealed storage of the secret, the last accepted time step, the attempt limits and the recovery codes are UserMFA's.
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumEnum to define valid hashing algorithms.Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intThe number of time periods either side of the current one for which a code is still accepted: 1 (RFC 6238 section 5.2).static final intThe default number of digits: 6.static final intThe default time period in seconds during which a code is valid: 30.static final intThe default number of characters in the secret: 32.Constructor Summary
ConstructorsConstructorDescriptionConstructor of the instance for a six digit code, using the system clock as time source.MFAUser(String appName, String account, String secret, MFAUser.Hashing algorithm, int digits, int period) Constructor of the instance of a user who is enrolled already, from the secret and the parameters that were saved when the enrolment was confirmed (SignIn L-6), using the system clock as time source.Method Summary
Modifier and TypeMethodDescriptionintLooks for the time step a refused code would have matched at, outside the windowvalidateCodeStep(String)accepts (CC-FIX-SIGNIN-TOTP-ENROL, item 9): the steps fromALLOWED_DISCREPANCY+1 tomaxStepsaway from the current one, on both sides, the nearest first.generateQRCodeData(ErrorCorrectionLevel errorCorrectionLevel) Generate the QR code data with relevant information with specified error correction level.Generate the secret with thesecretLengthspecified, defaultSECRET_LENGTH = 32.Gets the QR code data the authenticator app is enrolled with: the account, the issuer, the secret and the parameters, which are what is saved when the enrolment is confirmed and whatMFAUser(String,String,String,Hashing,int,int)takes them back from.Gets or generate the secret with thesecretLengthspecified, defaultSECRET_LENGTH = 32, if not previously done.Sets the algorithm to use when creating the QR code.setDigits(int digits) Sets the number of digits to use for the code.setPeriod(int period) Sets the time period in seconds during which a code is valid, default isPERIOD = 30.setSecretLength(int secretLength) Sets the number of Base32 characters of the secret to generate, default isSECRET_LENGTH = 32(160 bits, the RFC 4226 recommendation).booleanvalidateCode(String code) Validates a code against the generated secret, for the current time period and the period immediately before and after it (ALLOWED_DISCREPANCY = 1).longvalidateCodeStep(String code) Validates a code asvalidateCode(String)does, and tells the time step of the code it accepted.
Field Details
SECRET_LENGTH
public static final int SECRET_LENGTHThe default number of characters in the secret: 32.- See Also:
DIGITS
public static final int DIGITSThe default number of digits: 6.- See Also:
PERIOD
public static final int PERIODThe default time period in seconds during which a code is valid: 30.- See Also:
ALLOWED_DISCREPANCY
public static final int ALLOWED_DISCREPANCYThe number of time periods either side of the current one for which a code is still accepted: 1 (RFC 6238 section 5.2). The acceptance window is(2*ALLOWED_DISCREPANCY+1)*periodseconds, 90 seconds for the default period.- See Also:
Constructor Details
MFAUser
Constructor of the instance for a six digit code, using the system clock as time source.- Parameters:
appName- The application name as the issuer, in localized form (should never change).account- The account name is generally something to identify uniquely the user, generally an email address.- Throws:
NullPointerException- IfappNameoraccountisnull.
MFAUser
public MFAUser(String appName, String account, String secret, MFAUser.Hashing algorithm, int digits, int period) Constructor of the instance of a user who is enrolled already, from the secret and the parameters that were saved when the enrolment was confirmed (SignIn L-6), using the system clock as time source. Until SignIn L-6 a secret lived in the instance that generated it and nowhere else, so no code could be validated by another instance: not after a restart, and not by the sign-in of another session.- Parameters:
appName- The application name as the issuer.account- The account name.secret- The Base32 shared secret.algorithm- The hashing algorithm the authenticator app was enrolled with.digits- The number of digits it was enrolled with (range 4 to 8).period- The period in seconds it was enrolled with (range 10 to 60).- Throws:
NullPointerException- IfappName,account,secretoralgorithmisnull.IllegalArgumentException- If the secret is empty, or digits or period is not within its range.
Method Details
setDigits
Sets the number of digits to use for the code. Must be called beforegenerateSecret().Setting how many digits long the generated codes are:
Most TOTP implementations generate codes that are 6 digits long, but codes can have a length of any positive non-zero integer.
- Parameters:
digits- The requested number of digits (default 6, minimum 4 and maximum 8 is recommended).- Returns:
- "this" instance for concatenation.
- Throws:
IllegalArgumentException- If digits range is not within 4 to 8.IllegalStateException- If the secret has already been generated.
setPeriod
Sets the time period in seconds during which a code is valid, default isPERIOD = 30. Must be called beforegenerateSecret().Setting the time period:
The one time password codes generated in the authenticator apps only last for a certain time period before they are re-generated. Independently of the period, the code of the period immediately before and immediately after the current one is also accepted (
ALLOWED_DISCREPANCY = 1), to allow for a small time drift between the server and the authenticator app and for the time it takes to type the code.- Parameters:
period- The requested period in seconds (range is 10 to 60).- Returns:
- "this" instance for concatenation.
- Throws:
IllegalArgumentException- If period range is not within 10 to 60 seconds.IllegalStateException- If the secret has already been generated.
setAlgorithm
Sets the algorithm to use when creating the QR code. Must be called beforegenerateSecret().- Parameters:
alg- The hashing algorithm.- Returns:
- "this" instance for concatenation.
- Throws:
NullPointerException- Ifalgisnull.IllegalStateException- If the secret has already been generated.
setSecretLength
Sets the number of Base32 characters of the secret to generate, default isSECRET_LENGTH = 32(160 bits, the RFC 4226 recommendation). Must be called beforegenerateSecret().- Parameters:
secretLength- The requested secret length in characters (range is 32 to 128).- Returns:
- "this" instance for concatenation.
- Throws:
IllegalArgumentException- If secret length is not within 32 to 128 characters.IllegalStateException- If the secret has already been generated.
generateSecret
Generate the secret with thesecretLengthspecified, defaultSECRET_LENGTH = 32. The secret is only generated once, so there is no point in calling this method many times, nothing will happen.- Returns:
- "this" instance for concatenation.
getSecret
Gets or generate the secret with thesecretLengthspecified, defaultSECRET_LENGTH = 32, if not previously done.- Returns:
- The secret.
generateQRCodeData
public BarcodeURN generateQRCodeData(ErrorCorrectionLevel errorCorrectionLevel) throws BarcodeException Generate the QR code data with relevant information with specified error correction level.- Parameters:
errorCorrectionLevel-nullfor default L = Low = ~7% error correction level.- Returns:
- The generated barcode URN from where e.g. an SVG can be extracted.
- Throws:
IllegalStateException- If the secret is missing (has not been generated).BarcodeException- If there are missing or invalid parameters assigned in this builder encountered during barcode encoding.
getQRData
Gets the QR code data the authenticator app is enrolled with: the account, the issuer, the secret and the parameters, which are what is saved when the enrolment is confirmed and whatMFAUser(String,String,String,Hashing,int,int)takes them back from.- Returns:
- The immutable QR code data.
- Throws:
IllegalStateException- If the secret is missing (has not been generated).
validateCode
Validates a code against the generated secret, for the current time period and the period immediately before and after it (ALLOWED_DISCREPANCY = 1).The verification parameters (algorithm, digits, period) are taken from the QR code data the authenticator app was enrolled with, so they can never disagree with the app. Every candidate period is computed and compared in constant time; a
null, empty or whitespace-only code is never valid and never throws. Safe to call concurrently.- Parameters:
code- The code as typed by the user; whitespace is ignored.- Returns:
trueif the code is valid,falseotherwise.- Throws:
IllegalStateException- If the secret is missing (has not been generated): validating a user who was never enrolled is a caller error, not a wrong code.
validateCodeStep
Validates a code asvalidateCode(String)does, and tells the time step of the code it accepted. A code is valid for its own time step only, so the caller that keeps the step of the last code it accepted, and refuses every step that is not later, accepts each code once (RFC 6238 section 5.2).- Parameters:
code- The code as typed by the user; whitespace is ignored.- Returns:
- The time step of the code - the count of periods since the epoch - or
DefaultCodeVerifier.NO_STEPif the code is not valid. - Throws:
IllegalStateException- If the secret is missing (has not been generated).
driftOf
Looks for the time step a refused code would have matched at, outside the windowvalidateCodeStep(String)accepts (CC-FIX-SIGNIN-TOTP-ENROL, item 9): the steps fromALLOWED_DISCREPANCY+1 tomaxStepsaway from the current one, on both sides, the nearest first. Nothing is accepted here: the caller names the drift in its refusal, so that a user whose phone's clock is off is told so instead of guessing.- Parameters:
code- The code as typed by the user; whitespace is ignored.maxSteps- How many time steps away to look, at most.- Returns:
- The offset in time steps of the step the code matches at: positive when the clock of the code is ahead of this server's, negative when behind; zero when the code matches at none of them, and for no code.
- Throws:
IllegalStateException- If the secret is missing (has not been generated).