Class PushKeyFiles
PUSH-KEYFILES-DESIGN.md: the key directory (section 2), the references written in .iiziPush and in the credentials file's [server] section (3.2), and the one reader that turns a reference into key material (section 5) - for the server and the Designer alike. An application's .iiziPush and its jar carry paths, never a private key or a service-account JSON; the secret lives in a file of the key directory, readable by its owner only.The key directory (keyDirectory(), ruling K-1): the system property "iizi.push.keys", else the environment variable "IIZI_PUSH_KEYS", else <user.home>/.iizi/push-keys - the same precedence as the credentials file, with a default so that a developer configures nothing. A configured value that is not a path is SEVERE once and the default is used.
A reference (resolve(String), K-2) is a path as [server] fcmServiceAccountFile writes it - a leading ~ and every %NAME% expanded, %USERPROFILE% falling back to user.home - and a relative result is resolved against the key directory, on every machine that reads it (K-8). An absolute path is used as it is.
Reading (vapid(Path,VapidStorage), serviceAccount(Path)): a file is read when it is first needed and held until forget(Path) - the owner's watcher calls it when the file changes (section 7.3) - so a failed read is tried again at the next need, never by a timer. Before any byte is read, the permission rule of PUSH-CREDENTIALS.md section 8 applies to every key file (K-6): on a POSIX file system a file readable by group or others is refused; on Windows a broad ACL is a WARNING once per file. A VAPID key file is the $push container the server writes as server-keys.iiziPush (K-4), loaded through the caller's PushKeyFiles.VapidStorage, and is accepted when both keys are present, each validates and the two belong together; the FCM key file is Google's service-account JSON as downloaded.
Nothing leaks (section 5.5): no refusal, log line or toString carries a byte of a key file. A PushKeyFiles.Refusal names the file and the reason only.
Thread-safe: the cache is guarded by its own lock; the readers run under it.
- Author:
- Christopher Mindus
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classA key file that cannot be used: the file and the reason, never a byte of the file.static final recordA Firebase service-account key file as read.static final recordA VAPID key file as read: the pair it holds, validated and belonging together.static interfaceLoads a VAPID key file as its$pushcontainer: the server's property factory in production, the Designer's property manager, a test's own.static interfaceWatches a key file for the owner of the watcher: the engine'sPushFileWatcheron a server.Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringThe name of the default key directory insideDEFAULT_PARENT: "push-keys".static final StringThe default key directory belowuser.home: ".iizi", iiziGo's own user directory, then "push-keys".static final StringThe environment variable naming the key directory: "IIZI_PUSH_KEYS".static final StringThe POSIX mode of a key file: "rw-------", mode 0600.static final StringThe POSIX mode of an owner-only directory: "rwx------", mode 0700.static final StringThe system property naming the key directory: "iizi.push.keys".Constructor Summary
ConstructorsConstructorDescriptionConstructor over the JVM's configuration: the system properties, the environment anduser.home.PushKeyFiles(UnaryOperator<String> property, UnaryOperator<String> environment, String userHome) Constructor over supplied settings, for a test.Method Summary
Modifier and TypeMethodDescriptionvoidaddListener(Consumer<Path> listener) Adds a listener told, on the watcher's thread, that a watched file changed and was forgotten.static PathcreateOwnerOnly(Path file) Creates an empty key file owner-only: mode 0600 from the first byte on a POSIX file system, so there is no readable moment; a plain new file elsewhere, where the directory's ACL is inherited.static PathCreates a directory owner-only: mode 0700 from the start on a POSIX file system, so that no other user can ever list or enter it; a plain new directory elsewhere, where the parent's ACL is inherited.static PathexpandPath(String value) Expands a path of the credentials file, against the working directory: a leading~and every%NAME%from the environment,%USERPROFILE%falling back touser.homewhere the variable is absent (S-3); an unknown variable is left as written.voidDrops the held material of a file, so that the next need re-reads it; a file never read is a no-op.static booleanisBroadPrincipal(String name) Tells whether a principal name is one of the three the section 8 rule names, with or without its authority prefix, in any case.booleanTells whether a file's material is held, i.e.static booleanisMatchingPair(String publicKey, String privateKey) Checks that a VAPID pair belongs together: a signature made with the private key verifies under the public key (SHA256withECDSA, java.base only), the check the push services make at the first send.static booleanisServiceAccount(String text) Checks that a text is Google's service-account key file: JSON with"type": "service_account".Gets the key directory: the system property, else the environment variable, else the default<user.home>/.iizi/push-keys.Tells where the key directory comes from, for a page or a log line.static StringpathRefusal(String reference) Checks that a reference is a path, syntactically, without touching any file: whatPushProps.verifycan check on a machine that does not hold the key file.static StringposixRefusal(Path file) The POSIX refusal of a file: on a file system with POSIX permissions, a file that grants anything to group or others is refused, the rulesshapplies to private keys.static StringposixRefusal(Set<PosixFilePermission> permissions) The POSIX rule on a permission set: mode& 077non-zero is refused.booleanremoveListener(Consumer<Path> listener) Removes a listener.Resolves a reference to the file it names: blank is none;~and%NAME%are expanded; a relative path is taken under the key directory, an absolute one as it is.static StringNarrows a directory to its owner on a POSIX file system, for a directory the caller owns and created itself: a directory that grants anything to group or others is set to mode 0700.static StringrestrictToOwner(Path file) Narrows a file to its owner on a POSIX file system, for a file the caller owns and wrote itself (the server's own generated pair, design 9.3): a file that grants anything to group or others is set to mode 0600.serviceAccount(Path file) Reads a Firebase service-account key file, or answers it as held: Google's JSON as downloaded, accepted when"type"is"service_account"and it carriesclient_emailandprivate_key, the members the FCM authorization needs.voidsetWatch(PushKeyFiles.Watch watch) Sets the owner's watcher: every file read from now on is watched after its first successful read, and forgotten when it changes.vapid(Path file, PushKeyFiles.VapidStorage storage) Reads a VAPID key file, or answers it as held: the$pushcontainer loaded through the storage, accepted whenVPubKeyandVPrivKeyare present, each validates and the two belong together.voidWatches a file before it is read, so that its arrival is seen: what the credentials do for the files their[server]section names.static StringwindowsAclWarning(Path file) The Windows WARNING of a file: an ACL that grants read toEveryone,BUILTIN\UsersorAuthenticated Users.static StringwindowsAclWarning(List<AclEntry> acl) The Windows rule on an ACL: an ALLOW entry with READ_DATA for one of the three broad principals, matched on the English names.
Field Details
PROPERTY
The system property naming the key directory: "iizi.push.keys". Wins over the environment variable.- See Also:
ENVIRONMENT
The environment variable naming the key directory: "IIZI_PUSH_KEYS". Read when the property is absent.- See Also:
DEFAULT_PARENT
The default key directory belowuser.home: ".iizi", iiziGo's own user directory, then "push-keys".- See Also:
DEFAULT_NAME
The name of the default key directory insideDEFAULT_PARENT: "push-keys".- See Also:
OWNER_ONLY
OWNER_ONLY_DIRECTORY
The POSIX mode of an owner-only directory: "rwx------", mode 0700.- See Also:
Constructor Details
PushKeyFiles
public PushKeyFiles()Constructor over the JVM's configuration: the system properties, the environment anduser.home.PushKeyFiles
public PushKeyFiles(UnaryOperator<String> property, UnaryOperator<String> environment, String userHome) Constructor over supplied settings, for a test.- Parameters:
property- Answers a system property by name.environment- Answers an environment variable by name.userHome- The user's home directory.
Method Details
keyDirectory
Gets the key directory: the system property, else the environment variable, else the default<user.home>/.iizi/push-keys. A configured value that is not a path is SEVERE once, and the default is used. The directory need not exist.- Returns:
- The directory, absolute and normalized.
keyDirectorySource
Tells where the key directory comes from, for a page or a log line.- Returns:
"system property iizi.push.keys","environment variable IIZI_PUSH_KEYS"or"the default".
pathRefusal
Checks that a reference is a path, syntactically, without touching any file: whatPushProps.verifycan check on a machine that does not hold the key file.- Parameters:
reference- The reference as written, may be null.- Returns:
- null when it is a path; otherwise the refusal, e.g.
"is empty"or"is not a valid path (...)".
resolve
Resolves a reference to the file it names: blank is none;~and%NAME%are expanded; a relative path is taken under the key directory, an absolute one as it is.- Parameters:
reference- The reference as written, may be null or blank.- Returns:
- The absolute normalized path, or null when the reference is blank or not a path.
expandPath
Expands a path of the credentials file, against the working directory: a leading~and every%NAME%from the environment,%USERPROFILE%falling back touser.homewhere the variable is absent (S-3); an unknown variable is left as written. The Windows notation is accepted on every platform, so on a system whose separator is/(Linux, macOS) every backslash is taken as a separator:%USERPROFILE%\.iizi\key.jsonnames the same file there as on Windows instead of one file whose name contains backslashes. Moved fromPushCredentialsunchanged; a reference of a key file goes throughresolve(String)instead, which takes a relative path under the key directory.- Parameters:
value- The path as written, may be null or blank.- Returns:
- The absolute path, or null when absent or not a valid path.
vapid
public PushKeyFiles.VapidKeyFile vapid(Path file, PushKeyFiles.VapidStorage storage) throws PushKeyFiles.Refusal Reads a VAPID key file, or answers it as held: the$pushcontainer loaded through the storage, accepted whenVPubKeyandVPrivKeyare present, each validates and the two belong together. Whether the file's public key is the referencing container's is the caller's check.- Parameters:
file- The file, fromresolve(String).storage- The storage that loads the container.- Returns:
- The pair, held until the file changes.
- Throws:
PushKeyFiles.Refusal- When the file is missing, refused for its permissions, cannot be loaded, or holds no valid pair; the message never carries a key.
serviceAccount
Reads a Firebase service-account key file, or answers it as held: Google's JSON as downloaded, accepted when"type"is"service_account"and it carriesclient_emailandprivate_key, the members the FCM authorization needs. Whether itsproject_idis the referencing container'sfcmProjectis the caller's check.- Parameters:
file- The file, fromresolve(String).- Returns:
- The key file, held until it changes.
- Throws:
PushKeyFiles.Refusal- When the file is missing, refused for its permissions, cannot be read or is not a service-account key file; the message never carries its content.
watch
Watches a file before it is read, so that its arrival is seen: what the credentials do for the files their[server]section names. A file whose directory does not exist yet cannot be watched; it is then read at the next need and watched from its first successful read, as every file is. A no-op without a watcher, or for a file already watched.- Parameters:
file- The file, may be null for none.
forget
Drops the held material of a file, so that the next need re-reads it; a file never read is a no-op. The Windows ACL WARNING is logged again at that read.- Parameters:
file- The file.
isHeld
Tells whether a file's material is held, i.e. read and not forgotten since.- Parameters:
file- The file.- Returns:
- true when held.
setWatch
Sets the owner's watcher: every file read from now on is watched after its first successful read, and forgotten when it changes.- Parameters:
watch- The watcher, or null for none.
addListener
removeListener
posixRefusal
The POSIX refusal of a file: on a file system with POSIX permissions, a file that grants anything to group or others is refused, the rulesshapplies to private keys.- Parameters:
file- The file.- Returns:
- The refusal text, e.g.
"grants mode 0644 to group or others", or null when the file is fine or the file system has no POSIX permissions.
posixRefusal
The POSIX rule on a permission set: mode& 077non-zero is refused.- Parameters:
permissions- The permissions.- Returns:
- The refusal text naming the mode, or null when only the owner has any.
windowsAclWarning
The Windows WARNING of a file: an ACL that grants read toEveryone,BUILTIN\UsersorAuthenticated Users. The server cannot judge every ACL - a localized Windows names these principals differently - so it warns and does not refuse.- Parameters:
file- The file.- Returns:
- The warning text naming the principals, or null when none of the three reads or the file system has no ACLs.
windowsAclWarning
isBroadPrincipal
Tells whether a principal name is one of the three the section 8 rule names, with or without its authority prefix, in any case.- Parameters:
name- The principal name, may be null.- Returns:
- true for
Everyone,UsersorAuthenticated Users.
createOwnerOnly
Creates an empty key file owner-only: mode 0600 from the first byte on a POSIX file system, so there is no readable moment; a plain new file elsewhere, where the directory's ACL is inherited.- Parameters:
file- The file, which must not exist; its directory must.- Returns:
- The file.
- Throws:
IOException- If the file exists or cannot be created.
restrictToOwner
Narrows a file to its owner on a POSIX file system, for a file the caller owns and wrote itself (the server's own generated pair, design 9.3): a file that grants anything to group or others is set to mode 0600.- Parameters:
file- The file.- Returns:
- The former refusal text when the mode was narrowed, null when nothing was changed (the mode was fine, or the file system has no POSIX permissions).
- Throws:
IOException- If the permissions cannot be read or set.
createOwnerOnlyDirectory
Creates a directory owner-only: mode 0700 from the start on a POSIX file system, so that no other user can ever list or enter it; a plain new directory elsewhere, where the parent's ACL is inherited. Missing parents are created with the default permissions: only the last directory is the owner's. The directory twin ofcreateOwnerOnly(Path), for the secrets store (decision P-8 (a) ofSECRETS-STORE-DESIGN.md).- Parameters:
dir- The directory, which must not exist.- Returns:
- The directory.
- Throws:
IOException- If the directory exists or cannot be created.
restrictDirectoryToOwner
Narrows a directory to its owner on a POSIX file system, for a directory the caller owns and created itself: a directory that grants anything to group or others is set to mode 0700. The directory twin ofrestrictToOwner(Path).- Parameters:
dir- The directory.- Returns:
- The former refusal text when the mode was narrowed, null when nothing was changed (the mode was fine, or the file system has no POSIX permissions).
- Throws:
IOException- If the permissions cannot be read or set.
isServiceAccount
Checks that a text is Google's service-account key file: JSON with"type": "service_account".- Parameters:
text- The text.- Returns:
- true for a service-account key file.
isMatchingPair
Checks that a VAPID pair belongs together: a signature made with the private key verifies under the public key (SHA256withECDSA, java.base only), the check the push services make at the first send. The core twin of the server'sServerPushKeys.isMatchingPair, for the Designer, which cannot reach the server's key loader. Either key failing to validate or to load is a mismatch.- Parameters:
publicKey- The public key, base64url, may be null.privateKey- The private key, base64url, may be null.- Returns:
- true when the public key is the private key's.