Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion make/autoconf/lib-tests.m4
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
################################################################################

# Minimum supported versions
JTREG_MINIMUM_VERSION=7.5.1
JTREG_MINIMUM_VERSION=7.5.2
GTEST_MINIMUM_VERSION=1.14.0

###############################################################################
Expand Down
2 changes: 1 addition & 1 deletion make/conf/github-actions.conf
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
# Versions and download locations for dependencies used by GitHub Actions (GHA)

GTEST_VERSION=1.14.0
JTREG_VERSION=7.5.1+1
JTREG_VERSION=7.5.2+1

LINUX_X64_BOOT_JDK_EXT=tar.gz
LINUX_X64_BOOT_JDK_URL=https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.6%2B7/OpenJDK21U-jdk_x64_linux_hotspot_21.0.6_7.tar.gz
Expand Down
4 changes: 2 additions & 2 deletions make/conf/jib-profiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -1185,9 +1185,9 @@ var getJibProfilesDependencies = function (input, common) {
jtreg: {
server: "jpg",
product: "jtreg",
version: "7.5.1",
version: "7.5.2",
build_number: "1",
file: "bundles/jtreg-7.5.1+1.zip",
file: "bundles/jtreg-7.5.2+1.zip",
environment_name: "JT_HOME",
environment_path: input.get("jtreg", "home_path") + "/bin",
configure_args: "--with-jtreg=" + input.get("jtreg", "home_path"),
Expand Down
23 changes: 21 additions & 2 deletions src/java.base/share/classes/java/io/Console.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

package java.io;

import java.lang.annotation.Native;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
Expand Down Expand Up @@ -349,11 +350,17 @@ private static UnsupportedOperationException newUnsupportedOperationException()
"Console class itself does not provide implementation");
}

@Native static final int TTY_STDIN_MASK = 0x00000001;
@Native static final int TTY_STDOUT_MASK = 0x00000002;
@Native static final int TTY_STDERR_MASK = 0x00000004;
// ttyStatus() returns bit patterns above, a bit is set if the corresponding file
// descriptor is a character device
private static final int ttyStatus = ttyStatus();
private static native String encoding();
static final Charset CHARSET;
static {
Charset cs = null;
boolean istty = istty();
boolean istty = isStdinTty() && isStdoutTty();

if (istty) {
String csname = encoding();
Expand All @@ -378,6 +385,9 @@ private static UnsupportedOperationException newUnsupportedOperationException()
public Console console() {
return cons;
}
public boolean isStdinTty() {
return Console.isStdinTty();
}
});
}

Expand Down Expand Up @@ -419,5 +429,14 @@ private static Console instantiateConsole(boolean istty) {
}

private static final Console cons;
private static native boolean istty();
private static boolean isStdinTty() {
return (ttyStatus & TTY_STDIN_MASK) != 0;
}
private static boolean isStdoutTty() {
return (ttyStatus & TTY_STDOUT_MASK) != 0;
}
private static boolean isStderrTty() {
return (ttyStatus & TTY_STDERR_MASK) != 0;
}
private static native int ttyStatus();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -29,4 +29,5 @@

public interface JavaIOAccess {
Console console();
boolean isStdinTty();
}
62 changes: 59 additions & 3 deletions src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@
import java.util.Arrays;
import java.util.Formatter;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import jdk.internal.access.SharedSecrets;
import jdk.internal.util.StaticProperty;
import sun.nio.cs.StreamDecoder;
import sun.nio.cs.StreamEncoder;
import sun.nio.cs.UTF_8;

/**
* JdkConsole implementation based on the platform's TTY.
Expand Down Expand Up @@ -92,7 +96,55 @@ public String readLine() {
}

@Override
public char[] readPassword(String fmt, Object ... args) {
public char[] readPassword(String format, Object ... args) {
return readPassword0(false, format, args);
}

// These two methods are intended for sun.security.util.Password, so tools like keytool can
// use JdkConsoleImpl even when standard output is redirected. The Password class should first
// check if `System.console()` returns a Console instance and use it if available. Otherwise,
// it should call this method to obtain a JdkConsoleImpl. This ensures only one Console
// instance exists in the Java runtime.
private static final AtomicReference<Optional<JdkConsoleImpl>> INSTANCE = new AtomicReference<>();
public static Optional<JdkConsoleImpl> passwordConsole() {
Optional<JdkConsoleImpl> result = INSTANCE.get();
if (result != null) {
return result;
}

synchronized (JdkConsoleImpl.class) {
result = INSTANCE.get();
if (result != null) {
return result;
}

// If there's already a proper console, throw an exception
if (System.console() != null) {
throw new IllegalStateException("Can't create a dedicated password " +
"console since a real console already exists");
}

// If stdin is NOT redirected, return an Optional containing a JdkConsoleImpl
// instance, otherwise an empty Optional.
result = SharedSecrets.getJavaIOAccess().isStdinTty() ?
Optional.of(
new JdkConsoleImpl(
UTF_8.INSTANCE)) :
Optional.empty();

INSTANCE.set(result);
return result;
}
}

// Dedicated entry for sun.security.util.Password when stdout is redirected.
// This method strictly avoids producing any output by using noNewLine = true
// and an empty format string.
public char[] readPasswordNoNewLine() {
return readPassword0(true, "");
}

private char[] readPassword0(boolean noNewLine, String fmt, Object ... args) {
char[] passwd = null;
synchronized (writeLock) {
synchronized(readLock) {
Expand Down Expand Up @@ -120,7 +172,9 @@ public char[] readPassword(String fmt, Object ... args) {
ioe.addSuppressed(x);
}
if (ioe != null) {
Arrays.fill(passwd, ' ');
if (passwd != null) {
Arrays.fill(passwd, ' ');
}
try {
if (reader instanceof LineReader lr) {
lr.zeroOut();
Expand All @@ -131,7 +185,9 @@ public char[] readPassword(String fmt, Object ... args) {
throw ioe;
}
}
pw.println();
if (!noNewLine) {
pw.println();
}
}
}
return passwd;
Expand Down
138 changes: 92 additions & 46 deletions src/java.base/share/classes/sun/security/util/Password.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -29,11 +29,13 @@
import java.nio.*;
import java.nio.charset.*;
import java.util.Arrays;

import jdk.internal.access.SharedSecrets;
import jdk.internal.io.JdkConsoleImpl;
import jdk.internal.misc.VM;

/**
* A utility class for reading passwords
*
*/
public class Password {
/** Reads user password from given input stream. */
Expand All @@ -50,30 +52,39 @@ public static char[] readPassword(InputStream in, boolean isEchoOn)

char[] consoleEntered = null;
byte[] consoleBytes = null;
char[] buf = null;

try {
// Only use Console if `in` is the initial System.in
Console con;
if (!isEchoOn &&
in == SharedSecrets.getJavaLangAccess().initialSystemIn() &&
((con = System.console()) != null)) {
consoleEntered = con.readPassword();
// readPassword returns "" if you just press ENTER with the built-in Console,
// to be compatible with old Password class, change to null
if (consoleEntered == null || consoleEntered.length == 0) {
return null;
if (!isEchoOn) {
if (in == SharedSecrets.getJavaLangAccess().initialSystemIn()
&& ConsoleHolder.consoleIsAvailable()) {
consoleEntered = ConsoleHolder.readPassword();
// readPassword might return null. Stop now.
if (consoleEntered == null) {
return null;
}
consoleBytes = ConsoleHolder.convertToBytes(consoleEntered);
in = new ByteArrayInputStream(consoleBytes);
} else if (in == System.in && VM.isBooted()
&& System.in.available() == 0) {
// Warn if reading password from System.in but it's empty.
// This may be running in an IDE Run Window or in JShell,
// which acts like an interactive console and echoes the
// entered password. In this case, print a warning that
// the password might be echoed. If available() is not zero,
// it's more likely the input comes from a pipe, such as
// "echo password |" or "cat password_file |" where input
// will be silently consumed without echoing to the screen.
// Warn only if VM is booted and ResourcesMgr is available.
System.err.print(ResourcesMgr.getString
("warning.input.may.be.visible.on.screen"));
}
consoleBytes = convertToBytes(consoleEntered);
in = new ByteArrayInputStream(consoleBytes);
}

// Rest of the lines still necessary for KeyStoreLoginModule
// and when there is no console.

char[] lineBuffer;
char[] buf;

buf = lineBuffer = new char[128];
buf = new char[128];

int room = buf.length;
int offset = 0;
Expand Down Expand Up @@ -101,11 +112,11 @@ public static char[] readPassword(InputStream in, boolean isEchoOn)
/* fall through */
default:
if (--room < 0) {
char[] oldBuf = buf;
buf = new char[offset + 128];
room = buf.length - offset - 1;
System.arraycopy(lineBuffer, 0, buf, 0, offset);
Arrays.fill(lineBuffer, ' ');
lineBuffer = buf;
System.arraycopy(oldBuf, 0, buf, 0, offset);
Arrays.fill(oldBuf, ' ');
}
buf[offset++] = (char) c;
break;
Expand All @@ -118,8 +129,6 @@ public static char[] readPassword(InputStream in, boolean isEchoOn)

char[] ret = new char[offset];
System.arraycopy(buf, 0, ret, 0, offset);
Arrays.fill(buf, ' ');

return ret;
} finally {
if (consoleEntered != null) {
Expand All @@ -128,35 +137,72 @@ public static char[] readPassword(InputStream in, boolean isEchoOn)
if (consoleBytes != null) {
Arrays.fill(consoleBytes, (byte)0);
}
if (buf != null) {
Arrays.fill(buf, ' ');
}
}
}

/**
* Change a password read from Console.readPassword() into
* its original bytes.
*
* @param pass a char[]
* @return its byte[] format, similar to new String(pass).getBytes()
*/
private static byte[] convertToBytes(char[] pass) {
if (enc == null) {
synchronized (Password.class) {
enc = System.console()
.charset()
.newEncoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
// Everything on Console or JdkConsoleImpl is inside this class.
private static class ConsoleHolder {

// primary console; may be null
private static final Console c1;
// secondary console (when stdout is redirected); may be null
private static final JdkConsoleImpl c2;
// encoder for c1 or c2
private static final CharsetEncoder enc;

static {
c1 = System.console();
Charset charset;
if (c1 != null) {
c2 = null;
charset = c1.charset();
} else {
c2 = JdkConsoleImpl.passwordConsole().orElse(null);
charset = (c2 != null) ? c2.charset() : null;
}
enc = charset == null ? null : charset.newEncoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
}
byte[] ba = new byte[(int)(enc.maxBytesPerChar() * pass.length)];
ByteBuffer bb = ByteBuffer.wrap(ba);
synchronized (enc) {
enc.reset().encode(CharBuffer.wrap(pass), bb, true);

public static boolean consoleIsAvailable() {
return c1 != null || c2 != null;
}

public static char[] readPassword() {
assert consoleIsAvailable();
if (c1 != null) {
return c1.readPassword();
} else {
try {
return c2.readPasswordNoNewLine();
} finally {
System.err.println();
}
}
}
if (bb.position() < ba.length) {
ba[bb.position()] = '\n';

/**
* Convert a password read from console into its original bytes.
*
* @param pass a char[]
* @return its byte[] format, equivalent to new String(pass).getBytes()
* but String is immutable and cannot be cleaned up.
*/
public static byte[] convertToBytes(char[] pass) {
assert consoleIsAvailable();
byte[] ba = new byte[(int) (enc.maxBytesPerChar() * pass.length)];
ByteBuffer bb = ByteBuffer.wrap(ba);
synchronized (enc) {
enc.reset().encode(CharBuffer.wrap(pass), bb, true);
}
if (bb.remaining() > 0) {
bb.put((byte)'\n'); // will be recognized as a stop sign
}
return ba;
}
return ba;
}
private static volatile CharsetEncoder enc;
}
4 changes: 4 additions & 0 deletions src/java.base/share/classes/sun/security/util/Resources.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ public class Resources extends java.util.ListResourceBundle {
// sun.security.pkcs11.SunPKCS11
{"PKCS11.Token.providerName.Password.",
"PKCS11 Token [{0}] Password: "},

// sun.security.util.Password
{"warning.input.may.be.visible.on.screen",
"[WARNING: Input may be visible on screen]\u0020"},
};


Expand Down
Loading
Loading