Merge
This commit is contained in:
commit
f73a94a772
1
.hgtags
1
.hgtags
@ -573,4 +573,5 @@ e64383344f144217c36196c3c8a2df8f588a2af3 jdk-14+3
|
||||
19d0b382f0869f72d4381b54fa129f1c74b6e766 jdk-14+4
|
||||
3081f39a3d30d63b112098386ac2bb027c2b7223 jdk-13+29
|
||||
0f1e29c77e50c7da11d83df410026392c4d1a28c jdk-14+5
|
||||
2e63fb0a885fa908a97bbb0da8d7c3de11536aca jdk-13+30
|
||||
443f7359b34d60e7821216ffc60f88b6ffe0ccdd jdk-14+6
|
||||
|
@ -297,10 +297,7 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>All compilers are expected to be able to compile to the C99 language standard,
|
||||
as some C99 features are used in the source code. Microsoft Visual Studio
|
||||
doesn't fully support C99 so in practice shared code is limited to using C99
|
||||
features that it does support.</p>
|
||||
<p>All compilers are expected to be able to compile to the C99 language standard, as some C99 features are used in the source code. Microsoft Visual Studio doesn't fully support C99 so in practice shared code is limited to using C99 features that it does support.</p>
|
||||
<h3 id="gcc">gcc</h3>
|
||||
<p>The minimum accepted version of gcc is 4.8. Older versions will generate a warning by <code>configure</code> and are unlikely to work.</p>
|
||||
<p>The JDK is currently known to be able to compile with at least version 7.4 of gcc.</p>
|
||||
|
@ -154,6 +154,9 @@ TEST FAILURE</code></pre>
|
||||
<p>Use additional problem lists file or files, in addition to the default ProblemList.txt located at the JTReg test roots.</p>
|
||||
<p>If multiple file names are specified, they should be separated by space (or, to help avoid quoting issues, the special value <code>%20</code>).</p>
|
||||
<p>The file names should be either absolute, or relative to the JTReg test root of the tests to be run.</p>
|
||||
<h4 id="run_problem_lists">RUN_PROBLEM_LISTS</h4>
|
||||
<p>Use the problem lists to select tests instead of excluding them.</p>
|
||||
<p>Set to <code>true</code> or <code>false</code>. If <code>true</code>, JTReg will use <code>-match:</code> option, otherwise <code>-exclude:</code> will be used. Default is <code>false</code>.</p>
|
||||
<h4 id="options">OPTIONS</h4>
|
||||
<p>Additional options to the JTReg test framework.</p>
|
||||
<p>Use <code>JTREG="OPTIONS=--help all"</code> to see all available JTReg options.</p>
|
||||
|
@ -306,6 +306,14 @@ help avoid quoting issues, the special value `%20`).
|
||||
The file names should be either absolute, or relative to the JTReg test root of
|
||||
the tests to be run.
|
||||
|
||||
#### RUN_PROBLEM_LISTS
|
||||
|
||||
Use the problem lists to select tests instead of excluding them.
|
||||
|
||||
Set to `true` or `false`.
|
||||
If `true`, JTReg will use `-match:` option, otherwise `-exclude:` will be used.
|
||||
Default is `false`.
|
||||
|
||||
|
||||
#### OPTIONS
|
||||
Additional options to the JTReg test framework.
|
||||
|
@ -278,7 +278,7 @@ $(eval $(call SetTestOpt,TIMEOUT_FACTOR,JTREG))
|
||||
|
||||
$(eval $(call ParseKeywordVariable, JTREG, \
|
||||
SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR TEST_MODE ASSERT VERBOSE RETAIN \
|
||||
MAX_MEM, \
|
||||
MAX_MEM RUN_PROBLEM_LISTS, \
|
||||
STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \
|
||||
EXTRA_PROBLEM_LISTS AOT_MODULES, \
|
||||
))
|
||||
@ -828,6 +828,7 @@ define SetupRunJtregTestBody
|
||||
endif
|
||||
JTREG_VERBOSE ?= fail,error,summary
|
||||
JTREG_RETAIN ?= fail,error
|
||||
JTREG_RUN_PROBLEM_LISTS ?= false
|
||||
|
||||
ifneq ($$($1_JTREG_MAX_MEM), 0)
|
||||
$1_JTREG_BASIC_OPTIONS += -vmoption:-Xmx$$($1_JTREG_MAX_MEM)
|
||||
@ -865,13 +866,19 @@ define SetupRunJtregTestBody
|
||||
$1_JTREG_BASIC_OPTIONS += -nativepath:$$($1_JTREG_NATIVEPATH)
|
||||
endif
|
||||
|
||||
ifeq ($$(JTREG_RUN_PROBLEM_LISTS), true)
|
||||
JTREG_PROBLEM_LIST_PREFIX := -match:
|
||||
else
|
||||
JTREG_PROBLEM_LIST_PREFIX := -exclude:
|
||||
endif
|
||||
|
||||
ifneq ($$($1_JTREG_PROBLEM_LIST), )
|
||||
$1_JTREG_BASIC_OPTIONS += $$(addprefix -exclude:, $$($1_JTREG_PROBLEM_LIST))
|
||||
$1_JTREG_BASIC_OPTIONS += $$(addprefix $$(JTREG_PROBLEM_LIST_PREFIX), $$($1_JTREG_PROBLEM_LIST))
|
||||
endif
|
||||
|
||||
ifneq ($$(JTREG_EXTRA_PROBLEM_LISTS), )
|
||||
# Accept both absolute paths as well as relative to the current test root.
|
||||
$1_JTREG_BASIC_OPTIONS += $$(addprefix -exclude:, $$(wildcard \
|
||||
$1_JTREG_BASIC_OPTIONS += $$(addprefix $$(JTREG_PROBLEM_LIST_PREFIX), $$(wildcard \
|
||||
$$(JTREG_EXTRA_PROBLEM_LISTS) \
|
||||
$$(addprefix $$($1_TEST_ROOT)/, $$(JTREG_EXTRA_PROBLEM_LISTS)) \
|
||||
))
|
||||
|
@ -168,7 +168,7 @@ private:
|
||||
int state() const { return *_state_adr; }
|
||||
|
||||
// Non-virtual for speed
|
||||
bool _is_alive() const { return state() < zombie; }
|
||||
bool _is_alive() const { return state() < unloaded; }
|
||||
|
||||
virtual bool is_zombie() const { return state() == zombie; }
|
||||
virtual bool is_unloaded() const { return state() == unloaded; }
|
||||
|
@ -208,9 +208,9 @@ public:
|
||||
not_used = 1, // not entrant, but revivable
|
||||
not_entrant = 2, // marked for deoptimization but activations may still exist,
|
||||
// will be transformed to zombie when all activations are gone
|
||||
zombie = 3, // no activations exist, nmethod is ready for purge
|
||||
unloaded = 4 // there should be no activations, should not be called,
|
||||
// will be transformed to zombie immediately
|
||||
unloaded = 3, // there should be no activations, should not be called, will be
|
||||
// transformed to zombie by the sweeper, when not "locked in vm".
|
||||
zombie = 4 // no activations exist, nmethod is ready for purge
|
||||
};
|
||||
|
||||
virtual bool is_in_use() const = 0;
|
||||
|
@ -1136,6 +1136,20 @@ void nmethod::inc_decompile_count() {
|
||||
mdo->inc_decompile_count();
|
||||
}
|
||||
|
||||
bool nmethod::try_transition(int new_state_int) {
|
||||
signed char new_state = new_state_int;
|
||||
for (;;) {
|
||||
signed char old_state = Atomic::load(&_state);
|
||||
if (old_state >= new_state) {
|
||||
// Ensure monotonicity of transitions.
|
||||
return false;
|
||||
}
|
||||
if (Atomic::cmpxchg(new_state, &_state, old_state) == old_state) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void nmethod::make_unloaded() {
|
||||
post_compiled_method_unload();
|
||||
|
||||
@ -1159,7 +1173,9 @@ void nmethod::make_unloaded() {
|
||||
}
|
||||
// Unlink the osr method, so we do not look this up again
|
||||
if (is_osr_method()) {
|
||||
// Invalidate the osr nmethod only once
|
||||
// Invalidate the osr nmethod only once. Note that with concurrent
|
||||
// code cache unloading, OSR nmethods are invalidated before they
|
||||
// are made unloaded. Therefore, this becomes a no-op then.
|
||||
if (is_in_use()) {
|
||||
invalidate_osr_method();
|
||||
}
|
||||
@ -1213,12 +1229,14 @@ void nmethod::make_unloaded() {
|
||||
set_osr_link(NULL);
|
||||
NMethodSweeper::report_state_change(this);
|
||||
|
||||
// The release is only needed for compile-time ordering, as accesses
|
||||
// into the nmethod after the store are not safe due to the sweeper
|
||||
// being allowed to free it when the store is observed, during
|
||||
// concurrent nmethod unloading. Therefore, there is no need for
|
||||
// acquire on the loader side.
|
||||
OrderAccess::release_store(&_state, (signed char)unloaded);
|
||||
bool transition_success = try_transition(unloaded);
|
||||
|
||||
// It is an important invariant that there exists no race between
|
||||
// the sweeper and GC thread competing for making the same nmethod
|
||||
// zombie and unloaded respectively. This is ensured by
|
||||
// can_convert_to_zombie() returning false for any is_unloading()
|
||||
// nmethod, informing the sweeper not to step on any GC toes.
|
||||
assert(transition_success, "Invalid nmethod transition to unloaded");
|
||||
|
||||
#if INCLUDE_JVMCI
|
||||
// Clear the link between this nmethod and a HotSpotNmethod mirror
|
||||
@ -1283,7 +1301,7 @@ bool nmethod::make_not_entrant_or_zombie(int state) {
|
||||
assert(state == zombie || state == not_entrant, "must be zombie or not_entrant");
|
||||
assert(!is_zombie(), "should not already be a zombie");
|
||||
|
||||
if (_state == state) {
|
||||
if (Atomic::load(&_state) >= state) {
|
||||
// Avoid taking the lock if already in required state.
|
||||
// This is safe from races because the state is an end-state,
|
||||
// which the nmethod cannot back out of once entered.
|
||||
@ -1318,7 +1336,7 @@ bool nmethod::make_not_entrant_or_zombie(int state) {
|
||||
// Enter critical section. Does not block for safepoint.
|
||||
MutexLocker pl(Patching_lock, Mutex::_no_safepoint_check_flag);
|
||||
|
||||
if (_state == state) {
|
||||
if (Atomic::load(&_state) >= state) {
|
||||
// another thread already performed this transition so nothing
|
||||
// to do, but return false to indicate this.
|
||||
return false;
|
||||
@ -1354,7 +1372,18 @@ bool nmethod::make_not_entrant_or_zombie(int state) {
|
||||
}
|
||||
|
||||
// Change state
|
||||
_state = state;
|
||||
if (!try_transition(state)) {
|
||||
// If the transition fails, it is due to another thread making the nmethod more
|
||||
// dead. In particular, one thread might be making the nmethod unloaded concurrently.
|
||||
// If so, having patched in the jump in the verified entry unnecessarily is fine.
|
||||
// The nmethod is no longer possible to call by Java threads.
|
||||
// Incrementing the decompile count is also fine as the caller of make_not_entrant()
|
||||
// had a valid reason to deoptimize the nmethod.
|
||||
// Marking the nmethod as seen on stack also has no effect, as the nmethod is now
|
||||
// !is_alive(), and the seen on stack value is only used to convert not_entrant
|
||||
// nmethods to zombie in can_convert_to_zombie().
|
||||
return false;
|
||||
}
|
||||
|
||||
// Log the transition once
|
||||
log_state_change();
|
||||
|
@ -212,6 +212,9 @@ class nmethod : public CompiledMethod {
|
||||
void* operator new(size_t size, int nmethod_size, int comp_level) throw();
|
||||
|
||||
const char* reloc_string_for(u_char* begin, u_char* end);
|
||||
|
||||
bool try_transition(int new_state);
|
||||
|
||||
// Returns true if this thread changed the state of the nmethod or
|
||||
// false if another thread performed the transition.
|
||||
bool make_not_entrant_or_zombie(int state);
|
||||
@ -339,7 +342,7 @@ class nmethod : public CompiledMethod {
|
||||
// flag accessing and manipulation
|
||||
bool is_not_installed() const { return _state == not_installed; }
|
||||
bool is_in_use() const { return _state <= in_use; }
|
||||
bool is_alive() const { return _state < zombie; }
|
||||
bool is_alive() const { return _state < unloaded; }
|
||||
bool is_not_entrant() const { return _state == not_entrant; }
|
||||
bool is_zombie() const { return _state == zombie; }
|
||||
bool is_unloaded() const { return _state == unloaded; }
|
||||
|
@ -261,6 +261,24 @@ private:
|
||||
Atomic::store(true, &_failed);
|
||||
}
|
||||
|
||||
void unlink(nmethod* nm) {
|
||||
// Unlinking of the dependencies must happen before the
|
||||
// handshake separating unlink and purge.
|
||||
nm->flush_dependencies(false /* delete_immediately */);
|
||||
|
||||
// We don't need to take the lock when unlinking nmethods from
|
||||
// the Method, because it is only concurrently unlinked by
|
||||
// the entry barrier, which acquires the per nmethod lock.
|
||||
nm->unlink_from_method(false /* acquire_lock */);
|
||||
|
||||
if (nm->is_osr_method()) {
|
||||
// Invalidate the osr nmethod before the handshake. The nmethod
|
||||
// will be made unloaded after the handshake. Then invalidate_osr_method()
|
||||
// will be called again, which will be a no-op.
|
||||
nm->invalidate_osr_method();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
ZNMethodUnlinkClosure(bool unloading_occurred) :
|
||||
_unloading_occurred(unloading_occurred),
|
||||
@ -278,14 +296,7 @@ public:
|
||||
ZLocker<ZReentrantLock> locker(ZNMethod::lock_for_nmethod(nm));
|
||||
|
||||
if (nm->is_unloading()) {
|
||||
// Unlinking of the dependencies must happen before the
|
||||
// handshake separating unlink and purge.
|
||||
nm->flush_dependencies(false /* delete_immediately */);
|
||||
|
||||
// We don't need to take the lock when unlinking nmethods from
|
||||
// the Method, because it is only concurrently unlinked by
|
||||
// the entry barrier, which acquires the per nmethod lock.
|
||||
nm->unlink_from_method(false /* acquire_lock */);
|
||||
unlink(nm);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,14 @@ jobject JVMCI::make_global(const Handle& obj) {
|
||||
return res;
|
||||
}
|
||||
|
||||
void JVMCI::destroy_global(jobject handle) {
|
||||
// Assert before nulling out, for better debugging.
|
||||
assert(is_global_handle(handle), "precondition");
|
||||
oop* oop_ptr = reinterpret_cast<oop*>(handle);
|
||||
NativeAccess<>::oop_store(oop_ptr, (oop)NULL);
|
||||
object_handles()->release(oop_ptr);
|
||||
}
|
||||
|
||||
bool JVMCI::is_global_handle(jobject handle) {
|
||||
const oop* ptr = reinterpret_cast<oop*>(handle);
|
||||
return object_handles()->allocation_status(ptr) == OopStorage::ALLOCATED_ENTRY;
|
||||
|
@ -90,6 +90,7 @@ class JVMCI : public AllStatic {
|
||||
static void initialize_compiler(TRAPS);
|
||||
|
||||
static jobject make_global(const Handle& obj);
|
||||
static void destroy_global(jobject handle);
|
||||
static bool is_global_handle(jobject handle);
|
||||
|
||||
static jmetadata allocate_handle(const methodHandle& handle);
|
||||
|
@ -2208,8 +2208,7 @@ C2V_VMENTRY_NULL(jobject, getObject, (JNIEnv* env, jobject, jobject x, long disp
|
||||
C2V_VMENTRY(void, deleteGlobalHandle, (JNIEnv* env, jobject, jlong h))
|
||||
jobject handle = (jobject)(address)h;
|
||||
if (handle != NULL) {
|
||||
assert(JVMCI::is_global_handle(handle), "Invalid delete of global JNI handle");
|
||||
*((oop*)handle) = NULL; // Mark the handle as deleted, allocate will reuse it
|
||||
JVMCI::destroy_global(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2019, 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
|
||||
@ -35,7 +35,11 @@ import java.util.Enumeration;
|
||||
* it could be associated with a server or client who participates in many
|
||||
* sessions concurrently.
|
||||
* <p>
|
||||
* Not all environments will contain session contexts.
|
||||
* Not all environments will contain session contexts. For example, stateless
|
||||
* session resumption.
|
||||
* <p>
|
||||
* Session contexts may not contain all sessions. For example, stateless
|
||||
* sessions are not stored in the session context.
|
||||
* <p>
|
||||
* There are <code>SSLSessionContext</code> parameters that affect how
|
||||
* sessions are stored:
|
||||
@ -68,8 +72,11 @@ public interface SSLSessionContext {
|
||||
public SSLSession getSession(byte[] sessionId);
|
||||
|
||||
/**
|
||||
* Returns an Enumeration of all session id's grouped under this
|
||||
* Returns an Enumeration of all known session id's grouped under this
|
||||
* <code>SSLSessionContext</code>.
|
||||
* <p>Session contexts may not contain all sessions. For example,
|
||||
* stateless sessions are not stored in the session context.
|
||||
* <p>
|
||||
*
|
||||
* @return an enumeration of all the Session id's
|
||||
*/
|
||||
|
@ -482,7 +482,7 @@ final class Finished {
|
||||
shc.conContext.inputRecord.expectingFinishFlight();
|
||||
} else {
|
||||
if (shc.handshakeSession.isRejoinable() &&
|
||||
!shc.statelessResumption) {
|
||||
!shc.handshakeSession.isStatelessable(shc)) {
|
||||
((SSLSessionContextImpl)shc.sslContext.
|
||||
engineGetServerSessionContext()).put(
|
||||
shc.handshakeSession);
|
||||
@ -847,6 +847,8 @@ final class Finished {
|
||||
shc.conContext.serverVerifyData = fm.verifyData;
|
||||
}
|
||||
|
||||
shc.conContext.conSession = shc.handshakeSession.finish();
|
||||
|
||||
// update the context
|
||||
shc.handshakeConsumers.put(
|
||||
SSLHandshake.FINISHED.id, SSLHandshake.FINISHED);
|
||||
|
@ -45,7 +45,6 @@ import static sun.security.ssl.SSLHandshake.NEW_SESSION_TICKET;
|
||||
*/
|
||||
final class NewSessionTicket {
|
||||
static final int MAX_TICKET_LIFETIME = 604800; // seconds, 7 days
|
||||
|
||||
static final SSLConsumer handshakeConsumer =
|
||||
new T13NewSessionTicketConsumer();
|
||||
static final SSLConsumer handshake12Consumer =
|
||||
@ -60,7 +59,7 @@ final class NewSessionTicket {
|
||||
*/
|
||||
abstract static class NewSessionTicketMessage extends HandshakeMessage {
|
||||
int ticketLifetime;
|
||||
byte[] ticket;
|
||||
byte[] ticket = new byte[0];
|
||||
|
||||
NewSessionTicketMessage(HandshakeContext context) {
|
||||
super(context);
|
||||
@ -83,6 +82,9 @@ final class NewSessionTicket {
|
||||
"TicketNonce not part of RFC 5077.");
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return (ticket.length > 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* NewSessionTicket for TLS 1.2 and below (RFC 5077)
|
||||
@ -102,13 +104,13 @@ final class NewSessionTicket {
|
||||
|
||||
// RFC5077 struct {
|
||||
// uint32 ticket_lifetime;
|
||||
// opaque ticket<1..2^16-1>;
|
||||
// opaque ticket<0..2^16-1>;
|
||||
// } NewSessionTicket;
|
||||
|
||||
super(context);
|
||||
if (m.remaining() < 14) {
|
||||
if (m.remaining() < 6) {
|
||||
throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
||||
"Invalid NewSessionTicket message: no sufficient data");
|
||||
"Invalid NewSessionTicket message: insufficient data");
|
||||
}
|
||||
|
||||
this.ticketLifetime = Record.getInt32(m);
|
||||
@ -186,7 +188,7 @@ final class NewSessionTicket {
|
||||
|
||||
if (m.remaining() < 14) {
|
||||
throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
||||
"Invalid NewSessionTicket message: no sufficient data");
|
||||
"Invalid NewSessionTicket message: insufficient data");
|
||||
}
|
||||
|
||||
this.ticketLifetime = Record.getInt32(m);
|
||||
@ -195,18 +197,21 @@ final class NewSessionTicket {
|
||||
|
||||
if (m.remaining() < 5) {
|
||||
throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
||||
"Invalid NewSessionTicket message: no sufficient data");
|
||||
"Invalid NewSessionTicket message: insufficient ticket" +
|
||||
" data");
|
||||
}
|
||||
|
||||
this.ticket = Record.getBytes16(m);
|
||||
if (ticket.length == 0) {
|
||||
throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"No ticket in the NewSessionTicket handshake message");
|
||||
}
|
||||
}
|
||||
|
||||
if (m.remaining() < 2) {
|
||||
throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
||||
"Invalid NewSessionTicket message: no sufficient data");
|
||||
"Invalid NewSessionTicket message: extra data");
|
||||
}
|
||||
|
||||
SSLExtension[] supportedExtensions =
|
||||
@ -310,36 +315,45 @@ final class NewSessionTicket {
|
||||
|
||||
@Override
|
||||
public byte[] produce(ConnectionContext context) throws IOException {
|
||||
HandshakeContext hc = (HandshakeContext)context;
|
||||
|
||||
// The producing happens in server side only.
|
||||
ServerHandshakeContext shc = (ServerHandshakeContext)context;
|
||||
if (hc instanceof ServerHandshakeContext) {
|
||||
// Is this session resumable?
|
||||
if (!hc.handshakeSession.isRejoinable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Is this session resumable?
|
||||
if (!shc.handshakeSession.isRejoinable()) {
|
||||
return null;
|
||||
}
|
||||
// What's the requested PSK key exchange modes?
|
||||
//
|
||||
// Note that currently, the NewSessionTicket post-handshake is
|
||||
// produced and delivered only in the current handshake context
|
||||
// if required.
|
||||
PskKeyExchangeModesSpec pkemSpec =
|
||||
(PskKeyExchangeModesSpec) hc.handshakeExtensions.get(
|
||||
SSLExtension.PSK_KEY_EXCHANGE_MODES);
|
||||
if (pkemSpec == null || !pkemSpec.contains(
|
||||
PskKeyExchangeModesExtension.PskKeyExchangeMode.PSK_DHE_KE)) {
|
||||
// Client doesn't support PSK with (EC)DHE key establishment.
|
||||
return null;
|
||||
}
|
||||
} else { // PostHandshakeContext
|
||||
|
||||
// What's the requested PSK key exchange modes?
|
||||
//
|
||||
// Note that currently, the NewSessionTicket post-handshake is
|
||||
// produced and delivered only in the current handshake context
|
||||
// if required.
|
||||
PskKeyExchangeModesSpec pkemSpec =
|
||||
(PskKeyExchangeModesSpec)shc.handshakeExtensions.get(
|
||||
SSLExtension.PSK_KEY_EXCHANGE_MODES);
|
||||
if (pkemSpec == null || !pkemSpec.contains(
|
||||
PskKeyExchangeModesExtension.PskKeyExchangeMode.PSK_DHE_KE)) {
|
||||
// Client doesn't support PSK with (EC)DHE key establishment.
|
||||
return null;
|
||||
// Check if we have sent a PSK already, then we know it is using a
|
||||
// allowable PSK exchange key mode
|
||||
if (!hc.handshakeSession.isPSKable()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// get a new session ID
|
||||
SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
|
||||
shc.sslContext.engineGetServerSessionContext();
|
||||
hc.sslContext.engineGetServerSessionContext();
|
||||
SessionId newId = new SessionId(true,
|
||||
shc.sslContext.getSecureRandom());
|
||||
hc.sslContext.getSecureRandom());
|
||||
|
||||
SecretKey resumptionMasterSecret =
|
||||
shc.handshakeSession.getResumptionMasterSecret();
|
||||
hc.handshakeSession.getResumptionMasterSecret();
|
||||
if (resumptionMasterSecret == null) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
@ -349,10 +363,10 @@ final class NewSessionTicket {
|
||||
}
|
||||
|
||||
// construct the PSK and handshake message
|
||||
BigInteger nonce = shc.handshakeSession.incrTicketNonceCounter();
|
||||
BigInteger nonce = hc.handshakeSession.incrTicketNonceCounter();
|
||||
byte[] nonceArr = nonce.toByteArray();
|
||||
SecretKey psk = derivePreSharedKey(
|
||||
shc.negotiatedCipherSuite.hashAlg,
|
||||
hc.negotiatedCipherSuite.hashAlg,
|
||||
resumptionMasterSecret, nonceArr);
|
||||
|
||||
int sessionTimeoutSeconds = sessionCache.getSessionTimeout();
|
||||
@ -364,31 +378,35 @@ final class NewSessionTicket {
|
||||
return null;
|
||||
}
|
||||
|
||||
NewSessionTicketMessage nstm;
|
||||
NewSessionTicketMessage nstm = null;
|
||||
|
||||
SSLSessionImpl sessionCopy =
|
||||
new SSLSessionImpl(shc.handshakeSession, newId);
|
||||
new SSLSessionImpl(hc.handshakeSession, newId);
|
||||
sessionCopy.setPreSharedKey(psk);
|
||||
sessionCopy.setPskIdentity(newId.getId());
|
||||
|
||||
if (shc.statelessResumption) {
|
||||
try {
|
||||
nstm = new T13NewSessionTicketMessage(shc,
|
||||
sessionTimeoutSeconds, shc.sslContext.getSecureRandom(),
|
||||
nonceArr, new SessionTicketSpec().encrypt(shc, sessionCopy));
|
||||
// If a stateless ticket is allowed, attempt to make one
|
||||
if (hc.handshakeSession.isStatelessable(hc)) {
|
||||
nstm = new T13NewSessionTicketMessage(hc,
|
||||
sessionTimeoutSeconds,
|
||||
hc.sslContext.getSecureRandom(),
|
||||
nonceArr,
|
||||
new SessionTicketSpec().encrypt(hc, sessionCopy));
|
||||
// If ticket construction failed, switch to session cache
|
||||
if (!nstm.isValid()) {
|
||||
hc.statelessResumption = false;
|
||||
} else {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"Produced NewSessionTicket stateless " +
|
||||
"handshake message", nstm);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Error with NST ticket, abort NST
|
||||
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
nstm = new T13NewSessionTicketMessage(shc, sessionTimeoutSeconds,
|
||||
shc.sslContext.getSecureRandom(), nonceArr,
|
||||
}
|
||||
// If a session cache ticket is being used, make one
|
||||
if (!hc.handshakeSession.isStatelessable(hc)) {
|
||||
nstm = new T13NewSessionTicketMessage(hc, sessionTimeoutSeconds,
|
||||
hc.sslContext.getSecureRandom(), nonceArr,
|
||||
newId.getId());
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
@ -399,13 +417,21 @@ final class NewSessionTicket {
|
||||
// create and cache the new session
|
||||
// The new session must be a child of the existing session so
|
||||
// they will be invalidated together, etc.
|
||||
shc.handshakeSession.addChild(sessionCopy);
|
||||
hc.handshakeSession.addChild(sessionCopy);
|
||||
sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd());
|
||||
sessionCache.put(sessionCopy);
|
||||
}
|
||||
|
||||
// Output the handshake message.
|
||||
nstm.write(shc.handshakeOutput);
|
||||
shc.handshakeOutput.flush();
|
||||
if (nstm != null) {
|
||||
// should never be null
|
||||
nstm.write(hc.handshakeOutput);
|
||||
hc.handshakeOutput.flush();
|
||||
}
|
||||
|
||||
if (hc instanceof PostHandshakeContext) {
|
||||
((PostHandshakeContext) hc).finish();
|
||||
}
|
||||
|
||||
// The message has been delivered.
|
||||
return null;
|
||||
@ -448,23 +474,16 @@ final class NewSessionTicket {
|
||||
return null;
|
||||
}
|
||||
|
||||
NewSessionTicketMessage nstm;
|
||||
|
||||
SSLSessionImpl sessionCopy =
|
||||
new SSLSessionImpl(shc.handshakeSession, newId);
|
||||
sessionCopy.setPskIdentity(newId.getId());
|
||||
|
||||
try {
|
||||
nstm = new T12NewSessionTicketMessage(shc, sessionTimeoutSeconds,
|
||||
new SessionTicketSpec().encrypt(shc, sessionCopy));
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"Produced NewSessionTicket stateless handshake message", nstm);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Abort on error with NST ticket
|
||||
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
|
||||
return null;
|
||||
NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(shc,
|
||||
sessionTimeoutSeconds,
|
||||
new SessionTicketSpec().encrypt(shc, sessionCopy));
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"Produced NewSessionTicket stateless handshake message", nstm);
|
||||
}
|
||||
|
||||
// Output the handshake message.
|
||||
@ -505,6 +524,9 @@ final class NewSessionTicket {
|
||||
"Consuming NewSessionTicket message", nstm);
|
||||
}
|
||||
|
||||
SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
|
||||
hc.sslContext.engineGetClientSessionContext();
|
||||
|
||||
// discard tickets with timeout 0
|
||||
if (nstm.ticketLifetime <= 0 ||
|
||||
nstm.ticketLifetime > MAX_TICKET_LIFETIME) {
|
||||
@ -513,12 +535,10 @@ final class NewSessionTicket {
|
||||
"Discarding NewSessionTicket with lifetime "
|
||||
+ nstm.ticketLifetime, nstm);
|
||||
}
|
||||
sessionCache.remove(hc.handshakeSession.getSessionId());
|
||||
return;
|
||||
}
|
||||
|
||||
SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
|
||||
hc.sslContext.engineGetClientSessionContext();
|
||||
|
||||
if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
|
@ -54,6 +54,7 @@ final class PostHandshakeContext extends HandshakeContext {
|
||||
|
||||
handshakeConsumers = new LinkedHashMap<>(consumers);
|
||||
handshakeFinished = true;
|
||||
handshakeSession = context.conSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -82,4 +83,9 @@ final class PostHandshakeContext extends HandshakeContext {
|
||||
SSLHandshake.nameOf(handshakeType), be);
|
||||
}
|
||||
}
|
||||
|
||||
// Finish this PostHandshake event
|
||||
void finish() {
|
||||
handshakeSession = null;
|
||||
}
|
||||
}
|
||||
|
@ -344,6 +344,12 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
|
||||
hsStatus = tryKeyUpdate(hsStatus);
|
||||
}
|
||||
|
||||
// Check if NewSessionTicket PostHandshake message needs to be sent
|
||||
if (conContext.conSession.updateNST &&
|
||||
!conContext.sslConfig.isClientMode) {
|
||||
hsStatus = tryNewSessionTicket(hsStatus);
|
||||
}
|
||||
|
||||
// update context status
|
||||
ciphertext.handshakeStatus = hsStatus;
|
||||
|
||||
@ -397,6 +403,29 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
|
||||
return currentHandshakeStatus;
|
||||
}
|
||||
|
||||
// Try to generate a PostHandshake NewSessionTicket message. This is
|
||||
// TLS 1.3 only.
|
||||
private HandshakeStatus tryNewSessionTicket(
|
||||
HandshakeStatus currentHandshakeStatus) throws IOException {
|
||||
// Don't bother to kickstart if handshaking is in progress, or if the
|
||||
// connection is not duplex-open.
|
||||
if ((conContext.handshakeContext == null) &&
|
||||
conContext.protocolVersion.useTLS13PlusSpec() &&
|
||||
!conContext.isOutboundClosed() &&
|
||||
!conContext.isInboundClosed() &&
|
||||
!conContext.isBroken) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
|
||||
SSLLogger.finest("trigger NST");
|
||||
}
|
||||
conContext.conSession.updateNST = false;
|
||||
NewSessionTicket.kickstartProducer.produce(
|
||||
new PostHandshakeContext(conContext));
|
||||
return conContext.getHandshakeStatus();
|
||||
}
|
||||
|
||||
return currentHandshakeStatus;
|
||||
}
|
||||
|
||||
private static void checkParams(
|
||||
ByteBuffer[] srcs, int srcsOffset, int srcsLength,
|
||||
ByteBuffer[] dsts, int dstsOffset, int dstsLength) {
|
||||
|
@ -69,8 +69,8 @@ final class SSLSessionContextImpl implements SSLSessionContext {
|
||||
private int cacheLimit; // the max cache size
|
||||
private int timeout; // timeout in seconds
|
||||
|
||||
// Does this context support stateless session (RFC 5077)
|
||||
private boolean statelessSession = true;
|
||||
// Default setting for stateless session resumption support (RFC 5077)
|
||||
private boolean statelessSession = false;
|
||||
|
||||
// package private
|
||||
SSLSessionContextImpl(boolean server) {
|
||||
@ -234,15 +234,14 @@ final class SSLSessionContextImpl implements SSLSessionContext {
|
||||
// Property for Session Cache state
|
||||
if (server) {
|
||||
st = GetPropertyAction.privilegedGetProperty(
|
||||
"jdk.tls.server.enableSessionTicketExtension", "true");
|
||||
"jdk.tls.server.enableSessionTicketExtension", "false");
|
||||
} else {
|
||||
st = GetPropertyAction.privilegedGetProperty(
|
||||
"jdk.tls.client.enableSessionTicketExtension", "true");
|
||||
}
|
||||
if (st.compareToIgnoreCase("false") == 0) {
|
||||
statelessSession = false;
|
||||
"jdk.tls.client.enableSessionTicketExtension", "false");
|
||||
}
|
||||
|
||||
statelessSession = Boolean.parseBoolean(st);
|
||||
|
||||
// Property for Session Ticket Timeout. The value can be changed
|
||||
// by SSLSessionContext.setSessionTimeout(int)
|
||||
String s = GetPropertyAction.privilegedGetProperty(
|
||||
|
@ -27,6 +27,7 @@ package sun.security.ssl;
|
||||
import sun.security.x509.X509CertImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
@ -35,6 +36,7 @@ import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Queue;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -104,7 +106,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
private X509Certificate[] localCerts;
|
||||
private PrivateKey localPrivateKey;
|
||||
private final Collection<SignatureScheme> localSupportedSignAlgs;
|
||||
private String[] peerSupportedSignAlgs; // for certificate
|
||||
private Collection<SignatureScheme> peerSupportedSignAlgs; //for certificate
|
||||
private boolean useDefaultPeerSignAlgs = false;
|
||||
private List<byte[]> statusResponses;
|
||||
private SecretKey resumptionMasterSecret;
|
||||
@ -236,7 +238,8 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
baseSession.localSupportedSignAlgs == null ?
|
||||
Collections.emptySet() : baseSession.localSupportedSignAlgs;
|
||||
this.peerSupportedSignAlgs =
|
||||
baseSession.getPeerSupportedSignatureAlgorithms();
|
||||
baseSession.peerSupportedSignAlgs == null ?
|
||||
Collections.emptySet() : baseSession.peerSupportedSignAlgs;
|
||||
this.serverNameIndication = baseSession.serverNameIndication;
|
||||
this.requestedServerNames = baseSession.getRequestedServerNames();
|
||||
this.masterSecret = baseSession.getMasterSecret();
|
||||
@ -261,8 +264,10 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
/**
|
||||
* < 2 bytes > protocolVersion
|
||||
* < 2 bytes > cipherSuite
|
||||
* < 2 bytes > localSupportedSignAlgs entries
|
||||
* < 1 byte > localSupportedSignAlgs entries
|
||||
* < 2 bytes per entries > localSupportedSignAlgs
|
||||
* < 1 bytes > peerSupportedSignAlgs entries
|
||||
* < 2 bytes per entries > peerSupportedSignAlgs
|
||||
* < 2 bytes > preSharedKey length
|
||||
* < length in bytes > preSharedKey
|
||||
* < 1 byte > pskIdentity length
|
||||
@ -281,6 +286,9 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
* < 1 byte > ServerName length
|
||||
* < length in bytes > ServerName
|
||||
* < 4 bytes > creationTime
|
||||
* < 2 byte > status response length
|
||||
* < 2 byte > status response entry length
|
||||
* < length in byte > status response entry
|
||||
* < 1 byte > Length of peer host
|
||||
* < length in bytes > peer host
|
||||
* < 2 bytes> peer port
|
||||
@ -302,17 +310,17 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
* < length in bytes> PSK identity
|
||||
* Anonymous
|
||||
* < 1 byte >
|
||||
* < 4 bytes > maximumPacketSize
|
||||
* < 4 bytes > negotiatedMaxFragSize
|
||||
*/
|
||||
|
||||
SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException {
|
||||
int i = 0;
|
||||
byte[] b;
|
||||
|
||||
this.localSupportedSignAlgs = new ArrayList<>();
|
||||
|
||||
boundValues = null;
|
||||
|
||||
this.protocolVersion = ProtocolVersion.valueOf(Short.toUnsignedInt(buf.getShort()));
|
||||
boundValues = new ConcurrentHashMap<>();
|
||||
this.protocolVersion =
|
||||
ProtocolVersion.valueOf(Short.toUnsignedInt(buf.getShort()));
|
||||
|
||||
if (protocolVersion.useTLS13PlusSpec()) {
|
||||
this.sessionId = new SessionId(false, null);
|
||||
@ -322,14 +330,26 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
hc.sslContext.getSecureRandom());
|
||||
}
|
||||
|
||||
this.cipherSuite = CipherSuite.valueOf(Short.toUnsignedInt(buf.getShort()));
|
||||
this.cipherSuite =
|
||||
CipherSuite.valueOf(Short.toUnsignedInt(buf.getShort()));
|
||||
|
||||
// Local Supported signature algorithms
|
||||
i = Short.toUnsignedInt(buf.getShort());
|
||||
ArrayList<SignatureScheme> list = new ArrayList<>();
|
||||
i = Byte.toUnsignedInt(buf.get());
|
||||
while (i-- > 0) {
|
||||
this.localSupportedSignAlgs.add(SignatureScheme.valueOf(
|
||||
list.add(SignatureScheme.valueOf(
|
||||
Short.toUnsignedInt(buf.getShort())));
|
||||
}
|
||||
this.localSupportedSignAlgs = Collections.unmodifiableCollection(list);
|
||||
|
||||
// Peer Supported signature algorithms
|
||||
i = Byte.toUnsignedInt(buf.get());
|
||||
list.clear();
|
||||
while (i-- > 0) {
|
||||
list.add(SignatureScheme.valueOf(
|
||||
Short.toUnsignedInt(buf.getShort())));
|
||||
}
|
||||
this.peerSupportedSignAlgs = Collections.unmodifiableCollection(list);
|
||||
|
||||
// PSK
|
||||
i = Short.toUnsignedInt(buf.getShort());
|
||||
@ -410,9 +430,27 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
}
|
||||
}
|
||||
|
||||
maximumPacketSize = buf.getInt();
|
||||
negotiatedMaxFragLen = buf.getInt();
|
||||
|
||||
// Get creation time
|
||||
this.creationTime = buf.getLong();
|
||||
|
||||
// Get Buffer sizes
|
||||
|
||||
// Status Response
|
||||
len = Short.toUnsignedInt(buf.getShort());
|
||||
if (len == 0) {
|
||||
statusResponses = Collections.emptyList();
|
||||
} else {
|
||||
statusResponses = new ArrayList<>();
|
||||
}
|
||||
while (len-- > 0) {
|
||||
b = new byte[Short.toUnsignedInt(buf.getShort())];
|
||||
buf.get(b);
|
||||
statusResponses.add(b);
|
||||
}
|
||||
|
||||
// Get Peer host & port
|
||||
i = Byte.toUnsignedInt(buf.get());
|
||||
if (i == 0) {
|
||||
@ -484,6 +522,33 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
|
||||
context = (SSLSessionContextImpl)
|
||||
hc.sslContext.engineGetServerSessionContext();
|
||||
this.lastUsedTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// Some situations we cannot provide a stateless ticket, but after it
|
||||
// has been negotiated
|
||||
boolean isStatelessable(HandshakeContext hc) {
|
||||
if (!hc.statelessResumption) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no getMasterSecret with TLS1.2 or under, do not resume.
|
||||
if (!protocolVersion.useTLS13PlusSpec() &&
|
||||
getMasterSecret().getEncoded() == null) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.finest("No MasterSecret, cannot make stateless" +
|
||||
" ticket");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (boundValues != null && boundValues.size() > 0) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.finest("There are boundValues, cannot make" +
|
||||
" stateless ticket");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -497,11 +562,14 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
hos.putInt16(cipherSuite.id);
|
||||
|
||||
// Local Supported signature algorithms
|
||||
int l = localSupportedSignAlgs.size();
|
||||
hos.putInt16(l);
|
||||
SignatureScheme[] sig = new SignatureScheme[l];
|
||||
localSupportedSignAlgs.toArray(sig);
|
||||
for (SignatureScheme s : sig) {
|
||||
hos.putInt8(localSupportedSignAlgs.size());
|
||||
for (SignatureScheme s : localSupportedSignAlgs) {
|
||||
hos.putInt16(s.id);
|
||||
}
|
||||
|
||||
// Peer Supported signature algorithms
|
||||
hos.putInt8(peerSupportedSignAlgs.size());
|
||||
for (SignatureScheme s : peerSupportedSignAlgs) {
|
||||
hos.putInt16(s.id);
|
||||
}
|
||||
|
||||
@ -564,16 +632,30 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
// List of SNIServerName
|
||||
hos.putInt16(requestedServerNames.size());
|
||||
if (requestedServerNames.size() > 0) {
|
||||
for (SNIServerName host: requestedServerNames) {
|
||||
for (SNIServerName host : requestedServerNames) {
|
||||
b = host.getEncoded();
|
||||
hos.putInt8(b.length);
|
||||
hos.write(b, 0, b.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer sizes
|
||||
hos.putInt32(maximumPacketSize);
|
||||
hos.putInt32(negotiatedMaxFragLen);
|
||||
|
||||
// creation time
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
|
||||
hos.writeBytes(buffer.putLong(creationTime).array());
|
||||
|
||||
// Status Responses
|
||||
List<byte[]> list = getStatusResponses();
|
||||
int l = list.size();
|
||||
hos.putInt16(l);
|
||||
for (byte[] e : list) {
|
||||
hos.putInt16(e.length);
|
||||
hos.write(e);
|
||||
}
|
||||
|
||||
// peer Host & Port
|
||||
if (host == null || host.length() == 0) {
|
||||
hos.putInt8(0);
|
||||
@ -649,10 +731,14 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
|
||||
BigInteger incrTicketNonceCounter() {
|
||||
BigInteger result = ticketNonceCounter;
|
||||
ticketNonceCounter = ticketNonceCounter.add(BigInteger.valueOf(1));
|
||||
ticketNonceCounter = ticketNonceCounter.add(BigInteger.ONE);
|
||||
return result;
|
||||
}
|
||||
|
||||
boolean isPSKable() {
|
||||
return (ticketNonceCounter.compareTo(BigInteger.ZERO) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the master secret ... treat with extreme caution!
|
||||
*/
|
||||
@ -725,8 +811,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
|
||||
void setPeerSupportedSignatureAlgorithms(
|
||||
Collection<SignatureScheme> signatureSchemes) {
|
||||
peerSupportedSignAlgs =
|
||||
SignatureScheme.getAlgorithmNames(signatureSchemes);
|
||||
peerSupportedSignAlgs = signatureSchemes;
|
||||
}
|
||||
|
||||
// TLS 1.2 only
|
||||
@ -740,16 +825,20 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
// certificates and server key exchange), it MUST send the
|
||||
// signature_algorithms extension, listing the algorithms it
|
||||
// is willing to accept.
|
||||
private static final ArrayList<SignatureScheme> defaultPeerSupportedSignAlgs =
|
||||
new ArrayList<>(Arrays.asList(SignatureScheme.RSA_PKCS1_SHA1,
|
||||
SignatureScheme.DSA_SHA1,
|
||||
SignatureScheme.ECDSA_SHA1));
|
||||
|
||||
void setUseDefaultPeerSignAlgs() {
|
||||
useDefaultPeerSignAlgs = true;
|
||||
peerSupportedSignAlgs = new String[] {
|
||||
"SHA1withRSA", "SHA1withDSA", "SHA1withECDSA"};
|
||||
peerSupportedSignAlgs = defaultPeerSupportedSignAlgs;
|
||||
}
|
||||
|
||||
// Returns the connection session.
|
||||
SSLSessionImpl finish() {
|
||||
if (useDefaultPeerSignAlgs) {
|
||||
this.peerSupportedSignAlgs = new String[0];
|
||||
peerSupportedSignAlgs = Collections.emptySet();
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -1212,6 +1301,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
* sessions can be shared across different protection domains.
|
||||
*/
|
||||
private final ConcurrentHashMap<SecureKey, Object> boundValues;
|
||||
boolean updateNST;
|
||||
|
||||
/**
|
||||
* Assigns a session value. Session change events are given if
|
||||
@ -1238,6 +1328,9 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
e = new SSLSessionBindingEvent(this, key);
|
||||
((SSLSessionBindingListener)value).valueBound(e);
|
||||
}
|
||||
if (protocolVersion.useTLS13PlusSpec()) {
|
||||
updateNST = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1273,6 +1366,9 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
e = new SSLSessionBindingEvent(this, key);
|
||||
((SSLSessionBindingListener)value).valueUnbound(e);
|
||||
}
|
||||
if (protocolVersion.useTLS13PlusSpec()) {
|
||||
updateNST = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1474,11 +1570,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
*/
|
||||
@Override
|
||||
public String[] getPeerSupportedSignatureAlgorithms() {
|
||||
if (peerSupportedSignAlgs != null) {
|
||||
return peerSupportedSignAlgs.clone();
|
||||
}
|
||||
|
||||
return new String[0];
|
||||
return SignatureScheme.getAlgorithmNames(peerSupportedSignAlgs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1264,6 +1264,11 @@ public final class SSLSocketImpl
|
||||
conContext.outputRecord.writeCipher.atKeyLimit()) {
|
||||
tryKeyUpdate();
|
||||
}
|
||||
// Check if NewSessionTicket PostHandshake message needs to be sent
|
||||
if (conContext.conSession.updateNST) {
|
||||
conContext.conSession.updateNST = false;
|
||||
tryNewSessionTicket();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1499,6 +1504,25 @@ public final class SSLSocketImpl
|
||||
}
|
||||
}
|
||||
|
||||
// Try to generate a PostHandshake NewSessionTicket message. This is
|
||||
// TLS 1.3 only.
|
||||
private void tryNewSessionTicket() throws IOException {
|
||||
// Don't bother to kickstart if handshaking is in progress, or if the
|
||||
// connection is not duplex-open.
|
||||
if (!conContext.sslConfig.isClientMode &&
|
||||
conContext.protocolVersion.useTLS13PlusSpec() &&
|
||||
conContext.handshakeContext == null &&
|
||||
!conContext.isOutboundClosed() &&
|
||||
!conContext.isInboundClosed() &&
|
||||
!conContext.isBroken) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
|
||||
SSLLogger.finest("trigger new session ticket");
|
||||
}
|
||||
NewSessionTicket.kickstartProducer.produce(
|
||||
new PostHandshakeContext(conContext));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the handshaker and socket streams.
|
||||
*
|
||||
|
@ -1155,14 +1155,14 @@ final class ServerHello {
|
||||
chc, chc.resumingSession.getMasterSecret());
|
||||
}
|
||||
|
||||
chc.conContext.consumers.putIfAbsent(
|
||||
ContentType.CHANGE_CIPHER_SPEC.id,
|
||||
ChangeCipherSpec.t10Consumer);
|
||||
if (chc.statelessResumption) {
|
||||
chc.handshakeConsumers.putIfAbsent(
|
||||
SSLHandshake.NEW_SESSION_TICKET.id,
|
||||
SSLHandshake.NEW_SESSION_TICKET);
|
||||
}
|
||||
chc.conContext.consumers.putIfAbsent(
|
||||
ContentType.CHANGE_CIPHER_SPEC.id,
|
||||
ChangeCipherSpec.t10Consumer);
|
||||
chc.handshakeConsumers.put(
|
||||
SSLHandshake.FINISHED.id,
|
||||
SSLHandshake.FINISHED);
|
||||
|
@ -46,7 +46,6 @@ import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
@ -255,13 +254,17 @@ final class SessionTicketExtension {
|
||||
data = buf;
|
||||
}
|
||||
|
||||
public byte[] encrypt(HandshakeContext hc, SSLSessionImpl session)
|
||||
throws IOException {
|
||||
public byte[] encrypt(HandshakeContext hc, SSLSessionImpl session) {
|
||||
byte[] encrypted;
|
||||
StatelessKey key = KeyState.getCurrentKey(hc);
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
if (!hc.handshakeSession.isStatelessable(hc)) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
try {
|
||||
StatelessKey key = KeyState.getCurrentKey(hc);
|
||||
byte[] iv = new byte[16];
|
||||
|
||||
SecureRandom random = hc.sslContext.getSecureRandom();
|
||||
random.nextBytes(iv);
|
||||
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
@ -273,8 +276,11 @@ final class SessionTicketExtension {
|
||||
(byte)(key.num >>> 8),
|
||||
(byte)(key.num)}
|
||||
);
|
||||
encrypted = c.doFinal(session.write());
|
||||
|
||||
byte[] data = session.write();
|
||||
if (data.length == 0) {
|
||||
return data;
|
||||
}
|
||||
encrypted = c.doFinal(data);
|
||||
byte[] result = new byte[encrypted.length + Integer.BYTES +
|
||||
iv.length];
|
||||
result[0] = (byte)(key.num >>> 24);
|
||||
@ -286,7 +292,10 @@ final class SessionTicketExtension {
|
||||
Integer.BYTES + iv.length, encrypted.length);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw hc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, e);
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine("Encryption failed." + e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,11 +320,7 @@ final class SessionTicketExtension {
|
||||
(byte)(keyID >>> 8),
|
||||
(byte)(keyID)}
|
||||
);
|
||||
/*
|
||||
return ByteBuffer.wrap(c.doFinal(data,
|
||||
Integer.BYTES + iv.length,
|
||||
data.length - (Integer.BYTES + iv.length)));
|
||||
*/
|
||||
|
||||
ByteBuffer out;
|
||||
out = ByteBuffer.allocate(data.remaining() - GCM_TAG_LEN / 8);
|
||||
c.doFinal(data, out);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2019, 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
|
||||
@ -25,10 +25,12 @@
|
||||
|
||||
package sun.security.util;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@ -40,13 +42,27 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
*/
|
||||
public class ManifestDigester {
|
||||
|
||||
/**
|
||||
* The part "{@code Manifest-Main-Attributes}" of the main attributes
|
||||
* digest header name in a signature file as described in the jar
|
||||
* specification:
|
||||
* <blockquote>{@code x-Digest-Manifest-Main-Attributes}
|
||||
* (where x is the standard name of a {@link MessageDigest} algorithm):
|
||||
* The value of this attribute is the digest value of the main attributes
|
||||
* of the manifest.</blockquote>
|
||||
* @see <a href="{@docRoot}/../specs/jar/jar.html#signature-file">
|
||||
* JAR File Specification, section Signature File</a>
|
||||
* @see #getMainAttsEntry
|
||||
*/
|
||||
public static final String MF_MAIN_ATTRS = "Manifest-Main-Attributes";
|
||||
|
||||
/** the raw bytes of the manifest */
|
||||
private byte[] rawBytes;
|
||||
private final byte[] rawBytes;
|
||||
|
||||
/** the entries grouped by names */
|
||||
private HashMap<String, Entry> entries; // key is a UTF-8 string
|
||||
private final Entry mainAttsEntry;
|
||||
|
||||
/** individual sections by their names */
|
||||
private final HashMap<String, Entry> entries = new HashMap<>();
|
||||
|
||||
/** state returned by findSection */
|
||||
static class Position {
|
||||
@ -72,29 +88,31 @@ public class ManifestDigester {
|
||||
private boolean findSection(int offset, Position pos)
|
||||
{
|
||||
int i = offset, len = rawBytes.length;
|
||||
int last = offset;
|
||||
int last = offset - 1;
|
||||
int next;
|
||||
boolean allBlank = true;
|
||||
|
||||
pos.endOfFirstLine = -1;
|
||||
/* denotes that a position is not yet assigned.
|
||||
* As a primitive type int it cannot be null
|
||||
* and -1 would be confused with (i - 1) when i == 0 */
|
||||
final int UNASSIGNED = Integer.MIN_VALUE;
|
||||
|
||||
pos.endOfFirstLine = UNASSIGNED;
|
||||
|
||||
while (i < len) {
|
||||
byte b = rawBytes[i];
|
||||
switch(b) {
|
||||
case '\r':
|
||||
if (pos.endOfFirstLine == -1)
|
||||
if (pos.endOfFirstLine == UNASSIGNED)
|
||||
pos.endOfFirstLine = i-1;
|
||||
if ((i < len) && (rawBytes[i+1] == '\n'))
|
||||
if (i < len - 1 && rawBytes[i + 1] == '\n')
|
||||
i++;
|
||||
/* fall through */
|
||||
case '\n':
|
||||
if (pos.endOfFirstLine == -1)
|
||||
if (pos.endOfFirstLine == UNASSIGNED)
|
||||
pos.endOfFirstLine = i-1;
|
||||
if (allBlank || (i == len-1)) {
|
||||
if (i == len-1)
|
||||
pos.endOfSection = i;
|
||||
else
|
||||
pos.endOfSection = last;
|
||||
pos.endOfSection = allBlank ? last : i;
|
||||
pos.startOfNext = i+1;
|
||||
return true;
|
||||
}
|
||||
@ -116,16 +134,17 @@ public class ManifestDigester {
|
||||
public ManifestDigester(byte[] bytes)
|
||||
{
|
||||
rawBytes = bytes;
|
||||
entries = new HashMap<>();
|
||||
|
||||
Position pos = new Position();
|
||||
|
||||
if (!findSection(0, pos))
|
||||
if (!findSection(0, pos)) {
|
||||
mainAttsEntry = null;
|
||||
return; // XXX: exception?
|
||||
}
|
||||
|
||||
// create an entry for main attributes
|
||||
entries.put(MF_MAIN_ATTRS, new Entry().addSection(
|
||||
new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)));
|
||||
mainAttsEntry = new Entry().addSection(new Section(
|
||||
0, pos.endOfSection + 1, pos.startOfNext, rawBytes));
|
||||
|
||||
int start = pos.startOfNext;
|
||||
while(findSection(start, pos)) {
|
||||
@ -133,14 +152,16 @@ public class ManifestDigester {
|
||||
int sectionLen = pos.endOfSection-start+1;
|
||||
int sectionLenWithBlank = pos.startOfNext-start;
|
||||
|
||||
if (len > 6) {
|
||||
if (len >= 6) { // 6 == "Name: ".length()
|
||||
if (isNameAttr(bytes, start)) {
|
||||
ByteArrayOutputStream nameBuf = new ByteArrayOutputStream();
|
||||
nameBuf.write(bytes, start+6, len-6);
|
||||
|
||||
int i = start + len;
|
||||
if ((i-start) < sectionLen) {
|
||||
if (bytes[i] == '\r') {
|
||||
if (bytes[i] == '\r'
|
||||
&& i + 1 - start < sectionLen
|
||||
&& bytes[i + 1] == '\n') {
|
||||
i += 2;
|
||||
} else {
|
||||
i += 1;
|
||||
@ -152,14 +173,16 @@ public class ManifestDigester {
|
||||
// name is wrapped
|
||||
int wrapStart = i;
|
||||
while (((i-start) < sectionLen)
|
||||
&& (bytes[i++] != '\n'));
|
||||
if (bytes[i-1] != '\n')
|
||||
return; // XXX: exception?
|
||||
int wrapLen;
|
||||
if (bytes[i-2] == '\r')
|
||||
wrapLen = i-wrapStart-2;
|
||||
else
|
||||
wrapLen = i-wrapStart-1;
|
||||
&& (bytes[i] != '\r')
|
||||
&& (bytes[i] != '\n')) i++;
|
||||
int wrapLen = i - wrapStart;
|
||||
if (i - start < sectionLen) {
|
||||
i++;
|
||||
if (bytes[i - 1] == '\r'
|
||||
&& i - start < sectionLen
|
||||
&& bytes[i] == '\n')
|
||||
i++;
|
||||
}
|
||||
|
||||
nameBuf.write(bytes, wrapStart, wrapLen);
|
||||
} else {
|
||||
@ -167,7 +190,7 @@ public class ManifestDigester {
|
||||
}
|
||||
}
|
||||
|
||||
entries.computeIfAbsent(new String(nameBuf.toByteArray(), UTF_8),
|
||||
entries.computeIfAbsent(nameBuf.toString(UTF_8),
|
||||
dummy -> new Entry())
|
||||
.addSection(new Section(start, sectionLen,
|
||||
sectionLenWithBlank, rawBytes));
|
||||
@ -202,6 +225,26 @@ public class ManifestDigester {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the sections (particularly the last one of usually only one)
|
||||
* are properly delimited with a trailing blank line so that another
|
||||
* section can be correctly appended and return {@code true} or return
|
||||
* {@code false} to indicate that reproduction is not advised and should
|
||||
* be carried out with a clean "normalized" newly-written manifest.
|
||||
*
|
||||
* @see #reproduceRaw
|
||||
*/
|
||||
public boolean isProperlyDelimited() {
|
||||
return sections.stream().allMatch(
|
||||
Section::isProperlySectionDelimited);
|
||||
}
|
||||
|
||||
public void reproduceRaw(OutputStream out) throws IOException {
|
||||
for (Section sec : sections) {
|
||||
out.write(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] digest(MessageDigest md)
|
||||
{
|
||||
md.reset();
|
||||
@ -242,6 +285,15 @@ public class ManifestDigester {
|
||||
this.rawBytes = rawBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the raw section is terminated with a blank
|
||||
* line so that another section can possibly be appended resulting in a
|
||||
* valid manifest and {@code false} otherwise.
|
||||
*/
|
||||
private boolean isProperlySectionDelimited() {
|
||||
return lengthWithBlankLine > length;
|
||||
}
|
||||
|
||||
private static void doOldStyle(MessageDigest md,
|
||||
byte[] bytes,
|
||||
int offset,
|
||||
@ -268,10 +320,33 @@ public class ManifestDigester {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #MF_MAIN_ATTRS
|
||||
*/
|
||||
public Entry getMainAttsEntry() {
|
||||
return mainAttsEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #MF_MAIN_ATTRS
|
||||
*/
|
||||
public Entry getMainAttsEntry(boolean oldStyle) {
|
||||
mainAttsEntry.oldStyle = oldStyle;
|
||||
return mainAttsEntry;
|
||||
}
|
||||
|
||||
public Entry get(String name) {
|
||||
return entries.get(name);
|
||||
}
|
||||
|
||||
public Entry get(String name, boolean oldStyle) {
|
||||
Entry e = entries.get(name);
|
||||
if (e != null)
|
||||
Entry e = get(name);
|
||||
if (e == null && MF_MAIN_ATTRS.equals(name)) {
|
||||
e = getMainAttsEntry();
|
||||
}
|
||||
if (e != null) {
|
||||
e.oldStyle = oldStyle;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2019, 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
|
||||
@ -72,8 +72,7 @@ public class SignatureFileVerifier {
|
||||
private ArrayList<CodeSigner[]> signerCache;
|
||||
|
||||
private static final String ATTR_DIGEST =
|
||||
("-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS).toUpperCase
|
||||
(Locale.ENGLISH);
|
||||
"-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS.toUpperCase(Locale.ENGLISH);
|
||||
|
||||
/** the PKCS7 block for this .DSA/.RSA/.EC file */
|
||||
private PKCS7 block;
|
||||
@ -537,8 +536,7 @@ public class SignatureFileVerifier {
|
||||
|
||||
MessageDigest digest = getDigest(algorithm);
|
||||
if (digest != null) {
|
||||
ManifestDigester.Entry mde =
|
||||
md.get(ManifestDigester.MF_MAIN_ATTRS, false);
|
||||
ManifestDigester.Entry mde = md.getMainAttsEntry(false);
|
||||
byte[] computedHash = mde.digest(digest);
|
||||
byte[] expectedHash =
|
||||
Base64.getMimeDecoder().decode((String)se.getValue());
|
||||
|
@ -1,6 +1,7 @@
|
||||
## International Components for Unicode (ICU4J) v64.2
|
||||
|
||||
### ICU4J License
|
||||
```
|
||||
|
||||
COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later)
|
||||
|
||||
@ -387,3 +388,6 @@ Database section 7.
|
||||
# by ICANN or the IETF Trust on the database or the code. Any person
|
||||
# making a contribution to the database or code waives all rights to
|
||||
# future claims in that contribution or in the TZ Database.
|
||||
|
||||
```
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
## The Unicode Standard, Unicode Character Database, Version 12.1.0
|
||||
|
||||
### Unicode Character Database
|
||||
```
|
||||
|
||||
UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE
|
||||
|
||||
@ -48,3 +49,6 @@ Except as contained in this notice, the name of a copyright holder
|
||||
shall not be used in advertising or otherwise to promote the sale,
|
||||
use or other dealings in these Data Files or Software without prior
|
||||
written authorization of the copyright holder.
|
||||
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011, 2019, 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
|
||||
@ -345,7 +345,7 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ
|
||||
|
||||
if (krbcredsConstructor == 0) {
|
||||
krbcredsConstructor = (*env)->GetMethodID(env, krbcredsClass, "<init>",
|
||||
"(Lsun/security/krb5/internal/Ticket;Lsun/security/krb5/PrincipalName;Lsun/security/krb5/PrincipalName;Lsun/security/krb5/EncryptionKey;Lsun/security/krb5/internal/TicketFlags;Lsun/security/krb5/internal/KerberosTime;Lsun/security/krb5/internal/KerberosTime;Lsun/security/krb5/internal/KerberosTime;Lsun/security/krb5/internal/KerberosTime;Lsun/security/krb5/internal/HostAddresses;)V");
|
||||
"(Lsun/security/krb5/internal/Ticket;Lsun/security/krb5/PrincipalName;Lsun/security/krb5/PrincipalName;Lsun/security/krb5/PrincipalName;Lsun/security/krb5/PrincipalName;Lsun/security/krb5/EncryptionKey;Lsun/security/krb5/internal/TicketFlags;Lsun/security/krb5/internal/KerberosTime;Lsun/security/krb5/internal/KerberosTime;Lsun/security/krb5/internal/KerberosTime;Lsun/security/krb5/internal/KerberosTime;Lsun/security/krb5/internal/HostAddresses;)V");
|
||||
if (krbcredsConstructor == 0) {
|
||||
printf("Couldn't find sun.security.krb5.internal.Ticket constructor\n");
|
||||
break;
|
||||
@ -359,7 +359,9 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ
|
||||
krbcredsConstructor,
|
||||
ticket,
|
||||
clientPrincipal,
|
||||
NULL,
|
||||
targetPrincipal,
|
||||
NULL,
|
||||
encryptionKey,
|
||||
ticketFlags,
|
||||
authTime,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011, 2019, 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
|
||||
@ -26,8 +26,6 @@
|
||||
package javax.security.auth.kerberos;
|
||||
|
||||
import sun.security.krb5.JavaxSecurityAuthKerberosAccess;
|
||||
import sun.security.krb5.EncryptionKey;
|
||||
import sun.security.krb5.PrincipalName;
|
||||
|
||||
class JavaxSecurityAuthKerberosAccessImpl
|
||||
implements JavaxSecurityAuthKerberosAccess {
|
||||
@ -35,4 +33,20 @@ class JavaxSecurityAuthKerberosAccessImpl
|
||||
KeyTab ktab) {
|
||||
return ktab.takeSnapshot();
|
||||
}
|
||||
|
||||
public KerberosPrincipal kerberosTicketGetClientAlias(KerberosTicket t) {
|
||||
return t.clientAlias;
|
||||
}
|
||||
|
||||
public void kerberosTicketSetClientAlias(KerberosTicket t, KerberosPrincipal a) {
|
||||
t.clientAlias = a;
|
||||
}
|
||||
|
||||
public KerberosPrincipal kerberosTicketGetServerAlias(KerberosTicket t) {
|
||||
return t.serverAlias;
|
||||
}
|
||||
|
||||
public void kerberosTicketSetServerAlias(KerberosTicket t, KerberosPrincipal a) {
|
||||
t.serverAlias = a;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2019, 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
|
||||
@ -195,6 +195,10 @@ public class KerberosTicket implements Destroyable, Refreshable,
|
||||
|
||||
private transient boolean destroyed = false;
|
||||
|
||||
transient KerberosPrincipal clientAlias = null;
|
||||
|
||||
transient KerberosPrincipal serverAlias = null;
|
||||
|
||||
/**
|
||||
* Constructs a {@code KerberosTicket} using credentials information that a
|
||||
* client either receives from a KDC or reads from a cache.
|
||||
@ -591,7 +595,11 @@ public class KerberosTicket implements Destroyable, Refreshable,
|
||||
try {
|
||||
krb5Creds = new sun.security.krb5.Credentials(asn1Encoding,
|
||||
client.getName(),
|
||||
(clientAlias != null ?
|
||||
clientAlias.getName() : null),
|
||||
server.getName(),
|
||||
(serverAlias != null ?
|
||||
serverAlias.getName() : null),
|
||||
sessionKey.getEncoded(),
|
||||
sessionKey.getKeyType(),
|
||||
flags,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2019, 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
|
||||
@ -713,14 +713,14 @@ class Krb5Context implements GSSContextSpi {
|
||||
if (subject != null &&
|
||||
!subject.isReadOnly()) {
|
||||
/*
|
||||
* Store the service credentials as
|
||||
* javax.security.auth.kerberos.KerberosTicket in
|
||||
* the Subject. We could wait till the context is
|
||||
* succesfully established; however it is easier
|
||||
* to do here and there is no harm indoing it here.
|
||||
*/
|
||||
* Store the service credentials as
|
||||
* javax.security.auth.kerberos.KerberosTicket in
|
||||
* the Subject. We could wait until the context is
|
||||
* successfully established; however it is easier
|
||||
* to do it here and there is no harm.
|
||||
*/
|
||||
final KerberosTicket kt =
|
||||
Krb5Util.credsToTicket(serviceCreds);
|
||||
Krb5Util.credsToTicket(serviceCreds);
|
||||
AccessController.doPrivileged (
|
||||
new java.security.PrivilegedAction<Void>() {
|
||||
public Void run() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2019, 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
|
||||
@ -59,7 +59,9 @@ public class Krb5InitCredential
|
||||
private Krb5InitCredential(Krb5NameElement name,
|
||||
byte[] asn1Encoding,
|
||||
KerberosPrincipal client,
|
||||
KerberosPrincipal clientAlias,
|
||||
KerberosPrincipal server,
|
||||
KerberosPrincipal serverAlias,
|
||||
byte[] sessionKey,
|
||||
int keyType,
|
||||
boolean[] flags,
|
||||
@ -80,14 +82,21 @@ public class Krb5InitCredential
|
||||
endTime,
|
||||
renewTill,
|
||||
clientAddresses);
|
||||
|
||||
KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketSetClientAlias(this, clientAlias);
|
||||
KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketSetServerAlias(this, serverAlias);
|
||||
this.name = name;
|
||||
|
||||
try {
|
||||
// Cache this for later use by the sun.security.krb5 package.
|
||||
krb5Credentials = new Credentials(asn1Encoding,
|
||||
client.getName(),
|
||||
(clientAlias != null ?
|
||||
clientAlias.getName() : null),
|
||||
server.getName(),
|
||||
(serverAlias != null ?
|
||||
serverAlias.getName() : null),
|
||||
sessionKey,
|
||||
keyType,
|
||||
flags,
|
||||
@ -110,7 +119,9 @@ public class Krb5InitCredential
|
||||
Credentials delegatedCred,
|
||||
byte[] asn1Encoding,
|
||||
KerberosPrincipal client,
|
||||
KerberosPrincipal clientAlias,
|
||||
KerberosPrincipal server,
|
||||
KerberosPrincipal serverAlias,
|
||||
byte[] sessionKey,
|
||||
int keyType,
|
||||
boolean[] flags,
|
||||
@ -131,7 +142,10 @@ public class Krb5InitCredential
|
||||
endTime,
|
||||
renewTill,
|
||||
clientAddresses);
|
||||
|
||||
KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketSetClientAlias(this, clientAlias);
|
||||
KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketSetServerAlias(this, serverAlias);
|
||||
this.name = name;
|
||||
// A delegated cred does not have all fields set. So do not try to
|
||||
// creat new Credentials out of the delegatedCred.
|
||||
@ -153,10 +167,18 @@ public class Krb5InitCredential
|
||||
Krb5MechFactory.NT_GSS_KRB5_PRINCIPAL);
|
||||
}
|
||||
|
||||
KerberosPrincipal clientAlias = KerberosSecrets
|
||||
.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketGetClientAlias(tgt);
|
||||
KerberosPrincipal serverAlias = KerberosSecrets
|
||||
.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketGetServerAlias(tgt);
|
||||
return new Krb5InitCredential(name,
|
||||
tgt.getEncoded(),
|
||||
tgt.getClient(),
|
||||
clientAlias,
|
||||
tgt.getServer(),
|
||||
serverAlias,
|
||||
tgt.getSessionKey().getEncoded(),
|
||||
tgt.getSessionKeyType(),
|
||||
tgt.getFlags(),
|
||||
@ -179,10 +201,14 @@ public class Krb5InitCredential
|
||||
*/
|
||||
|
||||
PrincipalName cPrinc = delegatedCred.getClient();
|
||||
PrincipalName cAPrinc = delegatedCred.getClientAlias();
|
||||
PrincipalName sPrinc = delegatedCred.getServer();
|
||||
PrincipalName sAPrinc = delegatedCred.getServerAlias();
|
||||
|
||||
KerberosPrincipal client = null;
|
||||
KerberosPrincipal clientAlias = null;
|
||||
KerberosPrincipal server = null;
|
||||
KerberosPrincipal serverAlias = null;
|
||||
|
||||
Krb5NameElement credName = null;
|
||||
|
||||
@ -193,6 +219,10 @@ public class Krb5InitCredential
|
||||
client = new KerberosPrincipal(fullName);
|
||||
}
|
||||
|
||||
if (cAPrinc != null) {
|
||||
clientAlias = new KerberosPrincipal(cAPrinc.getName());
|
||||
}
|
||||
|
||||
// XXX Compare name to credName
|
||||
|
||||
if (sPrinc != null) {
|
||||
@ -201,11 +231,17 @@ public class Krb5InitCredential
|
||||
KerberosPrincipal.KRB_NT_SRV_INST);
|
||||
}
|
||||
|
||||
if (sAPrinc != null) {
|
||||
serverAlias = new KerberosPrincipal(sAPrinc.getName());
|
||||
}
|
||||
|
||||
return new Krb5InitCredential(credName,
|
||||
delegatedCred,
|
||||
delegatedCred.getEncoded(),
|
||||
client,
|
||||
clientAlias,
|
||||
server,
|
||||
serverAlias,
|
||||
sessionKey.getBytes(),
|
||||
sessionKey.getEType(),
|
||||
delegatedCred.getFlags(),
|
||||
|
@ -132,7 +132,7 @@ public class Krb5Util {
|
||||
|
||||
public static KerberosTicket credsToTicket(Credentials serviceCreds) {
|
||||
EncryptionKey sessionKey = serviceCreds.getSessionKey();
|
||||
return new KerberosTicket(
|
||||
KerberosTicket kt = new KerberosTicket(
|
||||
serviceCreds.getEncoded(),
|
||||
new KerberosPrincipal(serviceCreds.getClient().getName()),
|
||||
new KerberosPrincipal(serviceCreds.getServer().getName(),
|
||||
@ -145,14 +145,35 @@ public class Krb5Util {
|
||||
serviceCreds.getEndTime(),
|
||||
serviceCreds.getRenewTill(),
|
||||
serviceCreds.getClientAddresses());
|
||||
PrincipalName clientAlias = serviceCreds.getClientAlias();
|
||||
PrincipalName serverAlias = serviceCreds.getServerAlias();
|
||||
if (clientAlias != null) {
|
||||
KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketSetClientAlias(kt, new KerberosPrincipal(
|
||||
clientAlias.getName(), clientAlias.getNameType()));
|
||||
}
|
||||
if (serverAlias != null) {
|
||||
KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketSetServerAlias(kt, new KerberosPrincipal(
|
||||
serverAlias.getName(), serverAlias.getNameType()));
|
||||
}
|
||||
return kt;
|
||||
};
|
||||
|
||||
public static Credentials ticketToCreds(KerberosTicket kerbTicket)
|
||||
throws KrbException, IOException {
|
||||
KerberosPrincipal clientAlias = KerberosSecrets
|
||||
.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketGetClientAlias(kerbTicket);
|
||||
KerberosPrincipal serverAlias = KerberosSecrets
|
||||
.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketGetServerAlias(kerbTicket);
|
||||
return new Credentials(
|
||||
kerbTicket.getEncoded(),
|
||||
kerbTicket.getClient().getName(),
|
||||
(clientAlias != null ? clientAlias.getName() : null),
|
||||
kerbTicket.getServer().getName(),
|
||||
(serverAlias != null ? serverAlias.getName() : null),
|
||||
kerbTicket.getSessionKey().getEncoded(),
|
||||
kerbTicket.getSessionKeyType(),
|
||||
kerbTicket.getFlags(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2002, 2019, 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
|
||||
@ -25,6 +25,8 @@
|
||||
|
||||
package sun.security.jgss.krb5;
|
||||
|
||||
import sun.security.krb5.KerberosSecrets;
|
||||
|
||||
import javax.security.auth.kerberos.KerberosTicket;
|
||||
import javax.security.auth.kerberos.KerberosKey;
|
||||
import javax.security.auth.Subject;
|
||||
@ -182,24 +184,45 @@ class SubjectComber {
|
||||
|
||||
}
|
||||
} else {
|
||||
KerberosPrincipal serverAlias = KerberosSecrets
|
||||
.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketGetServerAlias(ticket);
|
||||
if (serverPrincipal == null ||
|
||||
ticket.getServer().getName().equals(serverPrincipal)) {
|
||||
|
||||
ticket.getServer().getName().equals(serverPrincipal) ||
|
||||
(serverAlias != null &&
|
||||
serverPrincipal.equals(
|
||||
serverAlias.getName()))) {
|
||||
KerberosPrincipal clientAlias = KerberosSecrets
|
||||
.getJavaxSecurityAuthKerberosAccess()
|
||||
.kerberosTicketGetClientAlias(ticket);
|
||||
if (clientPrincipal == null ||
|
||||
clientPrincipal.equals(
|
||||
ticket.getClient().getName())) {
|
||||
ticket.getClient().getName()) ||
|
||||
(clientAlias != null &&
|
||||
clientPrincipal.equals(
|
||||
clientAlias.getName()))) {
|
||||
if (oneOnly) {
|
||||
return ticket;
|
||||
} else {
|
||||
// Record names so that tickets will
|
||||
// all belong to same principals
|
||||
if (clientPrincipal == null) {
|
||||
clientPrincipal =
|
||||
ticket.getClient().getName();
|
||||
if (clientAlias == null) {
|
||||
clientPrincipal =
|
||||
ticket.getClient().getName();
|
||||
} else {
|
||||
clientPrincipal =
|
||||
clientAlias.getName();
|
||||
}
|
||||
}
|
||||
if (serverPrincipal == null) {
|
||||
serverPrincipal =
|
||||
ticket.getServer().getName();
|
||||
if (serverAlias == null) {
|
||||
serverPrincipal =
|
||||
ticket.getServer().getName();
|
||||
} else {
|
||||
serverPrincipal =
|
||||
serverAlias.getName();
|
||||
}
|
||||
}
|
||||
answer.add(credClass.cast(ticket));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2019, 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
|
||||
@ -49,7 +49,9 @@ public class Credentials {
|
||||
|
||||
Ticket ticket;
|
||||
PrincipalName client;
|
||||
PrincipalName clientAlias;
|
||||
PrincipalName server;
|
||||
PrincipalName serverAlias;
|
||||
EncryptionKey key;
|
||||
TicketFlags flags;
|
||||
KerberosTime authTime;
|
||||
@ -69,7 +71,9 @@ public class Credentials {
|
||||
|
||||
public Credentials(Ticket new_ticket,
|
||||
PrincipalName new_client,
|
||||
PrincipalName new_client_alias,
|
||||
PrincipalName new_server,
|
||||
PrincipalName new_server_alias,
|
||||
EncryptionKey new_key,
|
||||
TicketFlags new_flags,
|
||||
KerberosTime authTime,
|
||||
@ -78,14 +82,17 @@ public class Credentials {
|
||||
KerberosTime renewTill,
|
||||
HostAddresses cAddr,
|
||||
AuthorizationData authzData) {
|
||||
this(new_ticket, new_client, new_server, new_key, new_flags,
|
||||
authTime, new_startTime, new_endTime, renewTill, cAddr);
|
||||
this(new_ticket, new_client, new_client_alias, new_server,
|
||||
new_server_alias, new_key, new_flags, authTime,
|
||||
new_startTime, new_endTime, renewTill, cAddr);
|
||||
this.authzData = authzData;
|
||||
}
|
||||
|
||||
public Credentials(Ticket new_ticket,
|
||||
PrincipalName new_client,
|
||||
PrincipalName new_client_alias,
|
||||
PrincipalName new_server,
|
||||
PrincipalName new_server_alias,
|
||||
EncryptionKey new_key,
|
||||
TicketFlags new_flags,
|
||||
KerberosTime authTime,
|
||||
@ -95,7 +102,9 @@ public class Credentials {
|
||||
HostAddresses cAddr) {
|
||||
ticket = new_ticket;
|
||||
client = new_client;
|
||||
clientAlias = new_client_alias;
|
||||
server = new_server;
|
||||
serverAlias = new_server_alias;
|
||||
key = new_key;
|
||||
flags = new_flags;
|
||||
this.authTime = authTime;
|
||||
@ -107,7 +116,9 @@ public class Credentials {
|
||||
|
||||
public Credentials(byte[] encoding,
|
||||
String client,
|
||||
String clientAlias,
|
||||
String server,
|
||||
String serverAlias,
|
||||
byte[] keyBytes,
|
||||
int keyType,
|
||||
boolean[] flags,
|
||||
@ -118,7 +129,11 @@ public class Credentials {
|
||||
InetAddress[] cAddrs) throws KrbException, IOException {
|
||||
this(new Ticket(encoding),
|
||||
new PrincipalName(client, PrincipalName.KRB_NT_PRINCIPAL),
|
||||
(clientAlias == null? null : new PrincipalName(clientAlias,
|
||||
PrincipalName.KRB_NT_PRINCIPAL)),
|
||||
new PrincipalName(server, PrincipalName.KRB_NT_SRV_INST),
|
||||
(serverAlias == null? null : new PrincipalName(serverAlias,
|
||||
PrincipalName.KRB_NT_SRV_INST)),
|
||||
new EncryptionKey(keyType, keyBytes),
|
||||
(flags == null? null: new TicketFlags(flags)),
|
||||
(authTime == null? null: new KerberosTime(authTime)),
|
||||
@ -143,10 +158,18 @@ public class Credentials {
|
||||
return client;
|
||||
}
|
||||
|
||||
public final PrincipalName getClientAlias() {
|
||||
return clientAlias;
|
||||
}
|
||||
|
||||
public final PrincipalName getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public final PrincipalName getServerAlias() {
|
||||
return serverAlias;
|
||||
}
|
||||
|
||||
public final EncryptionKey getSessionKey() {
|
||||
return key;
|
||||
}
|
||||
@ -262,6 +285,7 @@ public class Credentials {
|
||||
return new KrbTgsReq(options,
|
||||
this,
|
||||
server,
|
||||
serverAlias,
|
||||
null, // from
|
||||
null, // till
|
||||
null, // rtime
|
||||
@ -484,7 +508,11 @@ public class Credentials {
|
||||
public static void printDebug(Credentials c) {
|
||||
System.out.println(">>> DEBUG: ----Credentials----");
|
||||
System.out.println("\tclient: " + c.client.toString());
|
||||
if (c.clientAlias != null)
|
||||
System.out.println("\tclient alias: " + c.clientAlias.toString());
|
||||
System.out.println("\tserver: " + c.server.toString());
|
||||
if (c.serverAlias != null)
|
||||
System.out.println("\tserver alias: " + c.serverAlias.toString());
|
||||
System.out.println("\tticket: sname: " + c.ticket.sname.toString());
|
||||
if (c.startTime != null) {
|
||||
System.out.println("\tstartTime: " + c.startTime.getTime());
|
||||
@ -512,7 +540,11 @@ public class Credentials {
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("Credentials:");
|
||||
sb.append( "\n client=").append(client);
|
||||
if (clientAlias != null)
|
||||
sb.append( "\n clientAlias=").append(clientAlias);
|
||||
sb.append( "\n server=").append(server);
|
||||
if (serverAlias != null)
|
||||
sb.append( "\n serverAlias=").append(serverAlias);
|
||||
if (authTime != null) {
|
||||
sb.append("\n authTime=").append(authTime);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011, 2019, 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
|
||||
@ -25,6 +25,8 @@
|
||||
|
||||
package sun.security.krb5;
|
||||
|
||||
import javax.security.auth.kerberos.KerberosPrincipal;
|
||||
import javax.security.auth.kerberos.KerberosTicket;
|
||||
import javax.security.auth.kerberos.KeyTab;
|
||||
import sun.security.krb5.EncryptionKey;
|
||||
import sun.security.krb5.PrincipalName;
|
||||
@ -39,4 +41,12 @@ public interface JavaxSecurityAuthKerberosAccess {
|
||||
*/
|
||||
public sun.security.krb5.internal.ktab.KeyTab keyTabTakeSnapshot(
|
||||
KeyTab ktab);
|
||||
|
||||
public KerberosPrincipal kerberosTicketGetClientAlias(KerberosTicket t);
|
||||
|
||||
public void kerberosTicketSetClientAlias(KerberosTicket t, KerberosPrincipal a);
|
||||
|
||||
public KerberosPrincipal kerberosTicketGetServerAlias(KerberosTicket t);
|
||||
|
||||
public void kerberosTicketSetServerAlias(KerberosTicket t, KerberosPrincipal a);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2019, 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
|
||||
@ -363,7 +363,9 @@ public class KrbApReq {
|
||||
creds = new Credentials(
|
||||
apReqMessg.ticket,
|
||||
authenticator.cname,
|
||||
null,
|
||||
apReqMessg.ticket.sname,
|
||||
null,
|
||||
enc_ticketPart.key,
|
||||
enc_ticketPart.flags,
|
||||
enc_ticketPart.authtime,
|
||||
|
@ -118,7 +118,7 @@ class KrbAsRep extends KrbKdcRep {
|
||||
"Cannot find key for type/kvno to decrypt AS REP - " +
|
||||
EType.toString(encPartKeyType) + "/" + encPartKvno);
|
||||
}
|
||||
decrypt(dkey, asReq);
|
||||
decrypt(dkey, asReq, cname);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,7 +136,7 @@ class KrbAsRep extends KrbKdcRep {
|
||||
password,
|
||||
encPartKeyType,
|
||||
PAData.getSaltAndParams(encPartKeyType, rep.pAData));
|
||||
decrypt(dkey, asReq);
|
||||
decrypt(dkey, asReq, cname);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,7 +144,8 @@ class KrbAsRep extends KrbKdcRep {
|
||||
* @param dkey the decryption key to use
|
||||
* @param asReq the original AS-REQ sent, used to validate AS-REP
|
||||
*/
|
||||
private void decrypt(EncryptionKey dkey, KrbAsReq asReq)
|
||||
private void decrypt(EncryptionKey dkey, KrbAsReq asReq,
|
||||
PrincipalName cname)
|
||||
throws KrbException, Asn1Exception, IOException {
|
||||
byte[] enc_as_rep_bytes = rep.encPart.decrypt(dkey,
|
||||
KeyUsage.KU_ENC_AS_REP_PART);
|
||||
@ -157,10 +158,16 @@ class KrbAsRep extends KrbKdcRep {
|
||||
ASReq req = asReq.getMessage();
|
||||
check(true, req, rep, dkey);
|
||||
|
||||
PrincipalName clientAlias = cname;
|
||||
if (clientAlias.equals(rep.cname))
|
||||
clientAlias = null;
|
||||
|
||||
creds = new Credentials(
|
||||
rep.ticket,
|
||||
rep.cname,
|
||||
clientAlias,
|
||||
enc_part.sname,
|
||||
null, // No server alias expected in a TGT
|
||||
enc_part.key,
|
||||
enc_part.flags,
|
||||
enc_part.authtime,
|
||||
|
@ -68,6 +68,7 @@ public final class KrbAsReqBuilder {
|
||||
// Common data for AS-REQ fields
|
||||
private KDCOptions options;
|
||||
private PrincipalName cname;
|
||||
private PrincipalName refCname; // May be changed by referrals
|
||||
private PrincipalName sname;
|
||||
private KerberosTime from;
|
||||
private KerberosTime till;
|
||||
@ -100,6 +101,7 @@ public final class KrbAsReqBuilder {
|
||||
private void init(PrincipalName cname)
|
||||
throws KrbException {
|
||||
this.cname = cname;
|
||||
this.refCname = cname;
|
||||
state = State.INIT;
|
||||
}
|
||||
|
||||
@ -284,7 +286,7 @@ public final class KrbAsReqBuilder {
|
||||
}
|
||||
return new KrbAsReq(key,
|
||||
options,
|
||||
cname,
|
||||
refCname,
|
||||
sname,
|
||||
from,
|
||||
till,
|
||||
@ -334,7 +336,7 @@ public final class KrbAsReqBuilder {
|
||||
ReferralsState referralsState = new ReferralsState();
|
||||
while (true) {
|
||||
if (referralsState.refreshComm()) {
|
||||
comm = new KdcComm(cname.getRealmAsString());
|
||||
comm = new KdcComm(refCname.getRealmAsString());
|
||||
}
|
||||
try {
|
||||
req = build(pakey, referralsState);
|
||||
@ -384,7 +386,7 @@ public final class KrbAsReqBuilder {
|
||||
|
||||
ReferralsState() throws KrbException {
|
||||
if (Config.DISABLE_REFERRALS) {
|
||||
if (cname.getNameType() == PrincipalName.KRB_NT_ENTERPRISE) {
|
||||
if (refCname.getNameType() == PrincipalName.KRB_NT_ENTERPRISE) {
|
||||
throw new KrbException("NT-ENTERPRISE principals only allowed" +
|
||||
" when referrals are enabled.");
|
||||
}
|
||||
@ -402,15 +404,15 @@ public final class KrbAsReqBuilder {
|
||||
if (req.getMessage().reqBody.kdcOptions.get(KDCOptions.CANONICALIZE) &&
|
||||
referredRealm != null && referredRealm.toString().length() > 0 &&
|
||||
count < Config.MAX_REFERRALS) {
|
||||
cname = new PrincipalName(cname.getNameType(),
|
||||
cname.getNameStrings(), referredRealm);
|
||||
refCname = new PrincipalName(refCname.getNameType(),
|
||||
refCname.getNameStrings(), referredRealm);
|
||||
refreshComm = true;
|
||||
count++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (count < Config.MAX_REFERRALS &&
|
||||
cname.getNameType() != PrincipalName.KRB_NT_ENTERPRISE) {
|
||||
refCname.getNameType() != PrincipalName.KRB_NT_ENTERPRISE) {
|
||||
// Server may raise an error if CANONICALIZE is true.
|
||||
// Try CANONICALIZE false.
|
||||
enabled = false;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2019, 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
|
||||
@ -76,7 +76,7 @@ public class KrbCred {
|
||||
options.set(KDCOptions.FORWARDABLE, true);
|
||||
|
||||
KrbTgsReq tgsReq = new KrbTgsReq(options, tgt, tgService,
|
||||
null, null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, // No easy way to get addresses right
|
||||
null, null, null);
|
||||
credMessg = createMessage(tgsReq.sendAndGetCreds(), key);
|
||||
@ -152,7 +152,7 @@ public class KrbCred {
|
||||
+ " endtime=" + endtime
|
||||
+ "renewTill=" + renewTill);
|
||||
}
|
||||
creds = new Credentials(ticket, pname, sname, credInfoKey,
|
||||
creds = new Credentials(ticket, pname, null, sname, null, credInfoKey,
|
||||
flags, authtime, starttime, endtime, renewTill, caddr);
|
||||
}
|
||||
|
||||
|
@ -86,9 +86,20 @@ public class KrbTgsRep extends KrbKdcRep {
|
||||
|
||||
check(false, req, rep, tgsReq.tgsReqKey);
|
||||
|
||||
PrincipalName serverAlias = tgsReq.getServerAlias();
|
||||
if (serverAlias != null) {
|
||||
PrincipalName repSname = enc_part.sname;
|
||||
if (serverAlias.equals(repSname) ||
|
||||
isReferralSname(repSname)) {
|
||||
serverAlias = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.creds = new Credentials(rep.ticket,
|
||||
rep.cname,
|
||||
tgsReq.getClientAlias(),
|
||||
enc_part.sname,
|
||||
serverAlias,
|
||||
enc_part.key,
|
||||
enc_part.flags,
|
||||
enc_part.authtime,
|
||||
@ -111,4 +122,16 @@ public class KrbTgsRep extends KrbKdcRep {
|
||||
sun.security.krb5.internal.ccache.Credentials setCredentials() {
|
||||
return new sun.security.krb5.internal.ccache.Credentials(rep, secondTicket);
|
||||
}
|
||||
|
||||
private static boolean isReferralSname(PrincipalName sname) {
|
||||
if (sname != null) {
|
||||
String[] snameStrings = sname.getNameStrings();
|
||||
if (snameStrings.length == 2 &&
|
||||
snameStrings[0].equals(
|
||||
PrincipalName.TGS_DEFAULT_SRV_NAME)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,9 @@ import java.util.Arrays;
|
||||
public class KrbTgsReq {
|
||||
|
||||
private PrincipalName princName;
|
||||
private PrincipalName clientAlias;
|
||||
private PrincipalName servName;
|
||||
private PrincipalName serverAlias;
|
||||
private TGSReq tgsReqMessg;
|
||||
private KerberosTime ctime;
|
||||
private Ticket secondTicket = null;
|
||||
@ -59,13 +61,16 @@ public class KrbTgsReq {
|
||||
|
||||
// Used in CredentialsUtil
|
||||
public KrbTgsReq(KDCOptions options, Credentials asCreds,
|
||||
PrincipalName cname, PrincipalName sname,
|
||||
PrincipalName cname, PrincipalName clientAlias,
|
||||
PrincipalName sname, PrincipalName serverAlias,
|
||||
Ticket[] additionalTickets, PAData[] extraPAs)
|
||||
throws KrbException, IOException {
|
||||
this(options,
|
||||
asCreds,
|
||||
cname,
|
||||
clientAlias,
|
||||
sname,
|
||||
serverAlias,
|
||||
null, // KerberosTime from
|
||||
null, // KerberosTime till
|
||||
null, // KerberosTime rtime
|
||||
@ -82,6 +87,7 @@ public class KrbTgsReq {
|
||||
KDCOptions options,
|
||||
Credentials asCreds,
|
||||
PrincipalName sname,
|
||||
PrincipalName serverAlias,
|
||||
KerberosTime from,
|
||||
KerberosTime till,
|
||||
KerberosTime rtime,
|
||||
@ -90,16 +96,18 @@ public class KrbTgsReq {
|
||||
AuthorizationData authorizationData,
|
||||
Ticket[] additionalTickets,
|
||||
EncryptionKey subKey) throws KrbException, IOException {
|
||||
this(options, asCreds, asCreds.getClient(), sname,
|
||||
from, till, rtime, eTypes, addresses,
|
||||
authorizationData, additionalTickets, subKey, null);
|
||||
this(options, asCreds, asCreds.getClient(), asCreds.getClientAlias(),
|
||||
sname, serverAlias, from, till, rtime, eTypes,
|
||||
addresses, authorizationData, additionalTickets, subKey, null);
|
||||
}
|
||||
|
||||
private KrbTgsReq(
|
||||
KDCOptions options,
|
||||
Credentials asCreds,
|
||||
PrincipalName cname,
|
||||
PrincipalName clientAlias,
|
||||
PrincipalName sname,
|
||||
PrincipalName serverAlias,
|
||||
KerberosTime from,
|
||||
KerberosTime till,
|
||||
KerberosTime rtime,
|
||||
@ -111,7 +119,9 @@ public class KrbTgsReq {
|
||||
PAData[] extraPAs) throws KrbException, IOException {
|
||||
|
||||
princName = cname;
|
||||
this.clientAlias = clientAlias;
|
||||
servName = sname;
|
||||
this.serverAlias = serverAlias;
|
||||
ctime = KerberosTime.now();
|
||||
|
||||
// check if they are valid arguments. The optional fields
|
||||
@ -365,6 +375,14 @@ public class KrbTgsReq {
|
||||
return secondTicket;
|
||||
}
|
||||
|
||||
PrincipalName getClientAlias() {
|
||||
return clientAlias;
|
||||
}
|
||||
|
||||
PrincipalName getServerAlias() {
|
||||
return serverAlias;
|
||||
}
|
||||
|
||||
private static void debug(String message) {
|
||||
// System.err.println(">>> KrbTgsReq: " + message);
|
||||
}
|
||||
|
@ -564,7 +564,9 @@ public class PrincipalName implements Cloneable {
|
||||
for (int i = 0; i < nameStrings.length; i++) {
|
||||
if (i > 0)
|
||||
str.append("/");
|
||||
str.append(nameStrings[i]);
|
||||
String n = nameStrings[i];
|
||||
n = n.replace("@", "\\@");
|
||||
str.append(n);
|
||||
}
|
||||
str.append("@");
|
||||
str.append(nameRealm.toString());
|
||||
|
@ -284,8 +284,9 @@ public class CredentialsUtil {
|
||||
// Try CANONICALIZE false.
|
||||
}
|
||||
}
|
||||
return serviceCredsSingle(options, asCreds,
|
||||
cname, sname, additionalTickets, extraPAs);
|
||||
return serviceCredsSingle(options, asCreds, cname,
|
||||
asCreds.getClientAlias(), sname, sname, additionalTickets,
|
||||
extraPAs);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -300,26 +301,29 @@ public class CredentialsUtil {
|
||||
options = new KDCOptions(options.toBooleanArray());
|
||||
options.set(KDCOptions.CANONICALIZE, true);
|
||||
PrincipalName cSname = sname;
|
||||
PrincipalName refSname = sname; // May change with referrals
|
||||
Credentials creds = null;
|
||||
boolean isReferral = false;
|
||||
List<String> referrals = new LinkedList<>();
|
||||
PrincipalName clientAlias = asCreds.getClientAlias();
|
||||
while (referrals.size() <= Config.MAX_REFERRALS) {
|
||||
ReferralsCache.ReferralCacheEntry ref =
|
||||
ReferralsCache.get(sname, cSname.getRealmString());
|
||||
ReferralsCache.get(cname, sname, refSname.getRealmString());
|
||||
String toRealm = null;
|
||||
if (ref == null) {
|
||||
creds = serviceCredsSingle(options, asCreds,
|
||||
cname, cSname, additionalTickets, extraPAs);
|
||||
creds = serviceCredsSingle(options, asCreds, cname,
|
||||
clientAlias, refSname, cSname, additionalTickets,
|
||||
extraPAs);
|
||||
PrincipalName server = creds.getServer();
|
||||
if (!cSname.equals(server)) {
|
||||
if (!refSname.equals(server)) {
|
||||
String[] serverNameStrings = server.getNameStrings();
|
||||
if (serverNameStrings.length == 2 &&
|
||||
serverNameStrings[0].equals(
|
||||
PrincipalName.TGS_DEFAULT_SRV_NAME) &&
|
||||
!cSname.getRealmAsString().equals(serverNameStrings[1])) {
|
||||
!refSname.getRealmAsString().equals(serverNameStrings[1])) {
|
||||
// Server Name (sname) has the following format:
|
||||
// krbtgt/TO-REALM.COM@FROM-REALM.COM
|
||||
ReferralsCache.put(sname, server.getRealmString(),
|
||||
ReferralsCache.put(cname, sname, server.getRealmString(),
|
||||
serverNameStrings[1], creds);
|
||||
toRealm = serverNameStrings[1];
|
||||
isReferral = true;
|
||||
@ -336,8 +340,8 @@ public class CredentialsUtil {
|
||||
// Referrals loop detected
|
||||
return null;
|
||||
}
|
||||
cSname = new PrincipalName(cSname.getNameString(),
|
||||
cSname.getNameType(), toRealm);
|
||||
refSname = new PrincipalName(refSname.getNameString(),
|
||||
refSname.getNameType(), toRealm);
|
||||
referrals.add(toRealm);
|
||||
isReferral = false;
|
||||
continue;
|
||||
@ -356,14 +360,15 @@ public class CredentialsUtil {
|
||||
*/
|
||||
private static Credentials serviceCredsSingle(
|
||||
KDCOptions options, Credentials asCreds,
|
||||
PrincipalName cname, PrincipalName sname,
|
||||
PrincipalName cname, PrincipalName clientAlias,
|
||||
PrincipalName refSname, PrincipalName sname,
|
||||
Ticket[] additionalTickets, PAData[] extraPAs)
|
||||
throws KrbException, IOException {
|
||||
Credentials theCreds = null;
|
||||
boolean[] okAsDelegate = new boolean[]{true};
|
||||
String[] serverAsCredsNames = asCreds.getServer().getNameStrings();
|
||||
String tgtRealm = serverAsCredsNames[1];
|
||||
String serviceRealm = sname.getRealmString();
|
||||
String serviceRealm = refSname.getRealmString();
|
||||
if (!serviceRealm.equals(tgtRealm)) {
|
||||
// This is a cross-realm service request
|
||||
if (DEBUG) {
|
||||
@ -390,8 +395,8 @@ public class CredentialsUtil {
|
||||
System.out.println(">>> Credentials serviceCredsSingle:" +
|
||||
" same realm");
|
||||
}
|
||||
KrbTgsReq req = new KrbTgsReq(options, asCreds,
|
||||
cname, sname, additionalTickets, extraPAs);
|
||||
KrbTgsReq req = new KrbTgsReq(options, asCreds, cname, clientAlias,
|
||||
refSname, sname, additionalTickets, extraPAs);
|
||||
theCreds = req.sendAndGetCreds();
|
||||
if (theCreds != null) {
|
||||
if (DEBUG) {
|
||||
|
@ -139,7 +139,7 @@ public class KRBError implements java.io.Serializable {
|
||||
sTime = new_sTime;
|
||||
suSec = new_suSec;
|
||||
errorCode = new_errorCode;
|
||||
crealm = new_cname.getRealm();
|
||||
crealm = new_cname != null ? new_cname.getRealm() : null;
|
||||
cname = new_cname;
|
||||
sname = new_sname;
|
||||
eText = new_eText;
|
||||
@ -168,7 +168,7 @@ public class KRBError implements java.io.Serializable {
|
||||
sTime = new_sTime;
|
||||
suSec = new_suSec;
|
||||
errorCode = new_errorCode;
|
||||
crealm = new_cname.getRealm();
|
||||
crealm = new_cname != null ? new_cname.getRealm() : null;
|
||||
cname = new_cname;
|
||||
sname = new_sname;
|
||||
eText = new_eText;
|
||||
|
@ -45,8 +45,27 @@ import sun.security.krb5.PrincipalName;
|
||||
*/
|
||||
final class ReferralsCache {
|
||||
|
||||
private static Map<PrincipalName, Map<String, ReferralCacheEntry>> referralsMap =
|
||||
new HashMap<>();
|
||||
private static Map<ReferralCacheKey, Map<String, ReferralCacheEntry>>
|
||||
referralsMap = new HashMap<>();
|
||||
|
||||
static private final class ReferralCacheKey {
|
||||
private PrincipalName cname;
|
||||
private PrincipalName sname;
|
||||
ReferralCacheKey (PrincipalName cname, PrincipalName sname) {
|
||||
this.cname = cname;
|
||||
this.sname = sname;
|
||||
}
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof ReferralCacheKey))
|
||||
return false;
|
||||
ReferralCacheKey that = (ReferralCacheKey)other;
|
||||
return cname.equals(that.cname) &&
|
||||
sname.equals(that.sname);
|
||||
}
|
||||
public int hashCode() {
|
||||
return cname.hashCode() + sname.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
static final class ReferralCacheEntry {
|
||||
private final Credentials creds;
|
||||
@ -64,8 +83,9 @@ final class ReferralsCache {
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a new referral entry to the cache, including: service principal,
|
||||
* source KDC realm, destination KDC realm and referral TGT.
|
||||
* Add a new referral entry to the cache, including: client principal,
|
||||
* service principal, source KDC realm, destination KDC realm and
|
||||
* referral TGT.
|
||||
*
|
||||
* If a loop is generated when adding the new referral, the first hop is
|
||||
* automatically removed. For example, let's assume that adding a
|
||||
@ -73,16 +93,17 @@ final class ReferralsCache {
|
||||
* REALM-1.COM -> REALM-2.COM -> REALM-3.COM -> REALM-1.COM. Then,
|
||||
* REALM-1.COM -> REALM-2.COM referral entry is removed from the cache.
|
||||
*/
|
||||
static synchronized void put(PrincipalName service,
|
||||
static synchronized void put(PrincipalName cname, PrincipalName service,
|
||||
String fromRealm, String toRealm, Credentials creds) {
|
||||
pruneExpired(service);
|
||||
ReferralCacheKey k = new ReferralCacheKey(cname, service);
|
||||
pruneExpired(k);
|
||||
if (creds.getEndTime().before(new Date())) {
|
||||
return;
|
||||
}
|
||||
Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
|
||||
Map<String, ReferralCacheEntry> entries = referralsMap.get(k);
|
||||
if (entries == null) {
|
||||
entries = new HashMap<String, ReferralCacheEntry>();
|
||||
referralsMap.put(service, entries);
|
||||
referralsMap.put(k, entries);
|
||||
}
|
||||
entries.remove(fromRealm);
|
||||
ReferralCacheEntry newEntry = new ReferralCacheEntry(creds, toRealm);
|
||||
@ -103,13 +124,14 @@ final class ReferralsCache {
|
||||
}
|
||||
|
||||
/*
|
||||
* Obtain a referral entry from the cache given a service principal and a
|
||||
* source KDC realm.
|
||||
* Obtain a referral entry from the cache given a client principal,
|
||||
* service principal and a source KDC realm.
|
||||
*/
|
||||
static synchronized ReferralCacheEntry get(PrincipalName service,
|
||||
String fromRealm) {
|
||||
pruneExpired(service);
|
||||
Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
|
||||
static synchronized ReferralCacheEntry get(PrincipalName cname,
|
||||
PrincipalName service, String fromRealm) {
|
||||
ReferralCacheKey k = new ReferralCacheKey(cname, service);
|
||||
pruneExpired(k);
|
||||
Map<String, ReferralCacheEntry> entries = referralsMap.get(k);
|
||||
if (entries != null) {
|
||||
ReferralCacheEntry toRef = entries.get(fromRealm);
|
||||
if (toRef != null) {
|
||||
@ -122,9 +144,9 @@ final class ReferralsCache {
|
||||
/*
|
||||
* Remove referral entries from the cache when referral TGTs expire.
|
||||
*/
|
||||
private static void pruneExpired(PrincipalName service) {
|
||||
private static void pruneExpired(ReferralCacheKey k) {
|
||||
Date now = new Date();
|
||||
Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
|
||||
Map<String, ReferralCacheEntry> entries = referralsMap.get(k);
|
||||
if (entries != null) {
|
||||
for (Entry<String, ReferralCacheEntry> mapEntry :
|
||||
entries.entrySet()) {
|
||||
|
@ -180,8 +180,9 @@ public class Credentials {
|
||||
// is most likely to be the one in Authenticator in PA-TGS-REQ encoded
|
||||
// in TGS-REQ, therefore only stored with a service ticket. Currently
|
||||
// in Java, we only reads TGTs.
|
||||
return new sun.security.krb5.Credentials(ticket,
|
||||
cname, sname, key, flags, authtime, starttime, endtime, renewTill, caddr);
|
||||
return new sun.security.krb5.Credentials(ticket, cname, null, sname,
|
||||
null, key, flags, authtime, starttime, endtime, renewTill,
|
||||
caddr);
|
||||
}
|
||||
|
||||
public KerberosTime getStartTime() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2019, 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
|
||||
@ -406,6 +406,8 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ
|
||||
"(Lsun/security/krb5/internal/Ticket;"
|
||||
"Lsun/security/krb5/PrincipalName;"
|
||||
"Lsun/security/krb5/PrincipalName;"
|
||||
"Lsun/security/krb5/PrincipalName;"
|
||||
"Lsun/security/krb5/PrincipalName;"
|
||||
"Lsun/security/krb5/EncryptionKey;"
|
||||
"Lsun/security/krb5/internal/TicketFlags;"
|
||||
"Lsun/security/krb5/internal/KerberosTime;"
|
||||
@ -667,7 +669,9 @@ JNIEXPORT jobject JNICALL Java_sun_security_krb5_Credentials_acquireDefaultNativ
|
||||
krbcredsConstructor,
|
||||
ticket,
|
||||
clientPrincipal,
|
||||
NULL,
|
||||
targetPrincipal,
|
||||
NULL,
|
||||
encryptionKey,
|
||||
ticketFlags,
|
||||
authTime, // mdu
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2019, 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
|
||||
@ -671,26 +671,18 @@ public final class JarSigner {
|
||||
throw new AssertionError(asae);
|
||||
}
|
||||
|
||||
PrintStream ps = new PrintStream(os);
|
||||
ZipOutputStream zos = new ZipOutputStream(ps);
|
||||
ZipOutputStream zos = new ZipOutputStream(os);
|
||||
|
||||
Manifest manifest = new Manifest();
|
||||
Map<String, Attributes> mfEntries = manifest.getEntries();
|
||||
|
||||
// The Attributes of manifest before updating
|
||||
Attributes oldAttr = null;
|
||||
|
||||
boolean mfModified = false;
|
||||
boolean mfCreated = false;
|
||||
byte[] mfRawBytes = null;
|
||||
|
||||
// Check if manifest exists
|
||||
ZipEntry mfFile;
|
||||
if ((mfFile = getManifestFile(zipFile)) != null) {
|
||||
ZipEntry mfFile = getManifestFile(zipFile);
|
||||
boolean mfCreated = mfFile == null;
|
||||
if (!mfCreated) {
|
||||
// Manifest exists. Read its raw bytes.
|
||||
mfRawBytes = zipFile.getInputStream(mfFile).readAllBytes();
|
||||
manifest.read(new ByteArrayInputStream(mfRawBytes));
|
||||
oldAttr = (Attributes) (manifest.getMainAttributes().clone());
|
||||
} else {
|
||||
// Create new manifest
|
||||
Attributes mattr = manifest.getMainAttributes();
|
||||
@ -701,7 +693,6 @@ public final class JarSigner {
|
||||
mattr.putValue("Created-By", jdkVersion + " (" + javaVendor
|
||||
+ ")");
|
||||
mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
|
||||
mfCreated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -728,8 +719,12 @@ public final class JarSigner {
|
||||
// out first
|
||||
mfFiles.addElement(ze);
|
||||
|
||||
if (SignatureFileVerifier.isBlockOrSF(
|
||||
ze.getName().toUpperCase(Locale.ENGLISH))) {
|
||||
String zeNameUp = ze.getName().toUpperCase(Locale.ENGLISH);
|
||||
if (SignatureFileVerifier.isBlockOrSF(zeNameUp)
|
||||
// no need to preserve binary manifest portions
|
||||
// if the only existing signature will be replaced
|
||||
&& !zeNameUp.startsWith(SignatureFile
|
||||
.getBaseSignatureFilesName(signerName))) {
|
||||
wasSigned = true;
|
||||
}
|
||||
|
||||
@ -742,55 +737,69 @@ public final class JarSigner {
|
||||
if (manifest.getAttributes(ze.getName()) != null) {
|
||||
// jar entry is contained in manifest, check and
|
||||
// possibly update its digest attributes
|
||||
if (updateDigests(ze, zipFile, digests,
|
||||
manifest)) {
|
||||
mfModified = true;
|
||||
}
|
||||
updateDigests(ze, zipFile, digests, manifest);
|
||||
} else if (!ze.isDirectory()) {
|
||||
// Add entry to manifest
|
||||
Attributes attrs = getDigestAttributes(ze, zipFile, digests);
|
||||
mfEntries.put(ze.getName(), attrs);
|
||||
mfModified = true;
|
||||
manifest.getEntries().put(ze.getName(), attrs);
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate the manifest raw bytes if necessary
|
||||
if (mfModified) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
/*
|
||||
* Note:
|
||||
*
|
||||
* The Attributes object is based on HashMap and can handle
|
||||
* continuation lines. Therefore, even if the contents are not changed
|
||||
* (in a Map view), the bytes that it write() may be different from
|
||||
* the original bytes that it read() from. Since the signature is
|
||||
* based on raw bytes, we must retain the exact bytes.
|
||||
*/
|
||||
boolean mfModified;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (mfCreated || !wasSigned) {
|
||||
mfModified = true;
|
||||
manifest.write(baos);
|
||||
if (wasSigned) {
|
||||
byte[] newBytes = baos.toByteArray();
|
||||
if (mfRawBytes != null
|
||||
&& oldAttr.equals(manifest.getMainAttributes())) {
|
||||
mfRawBytes = baos.toByteArray();
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Note:
|
||||
*
|
||||
* The Attributes object is based on HashMap and can handle
|
||||
* continuation columns. Therefore, even if the contents are
|
||||
* not changed (in a Map view), the bytes that it write()
|
||||
* may be different from the original bytes that it read()
|
||||
* from. Since the signature on the main attributes is based
|
||||
* on raw bytes, we must retain the exact bytes.
|
||||
*/
|
||||
// the manifest before updating
|
||||
Manifest oldManifest = new Manifest(
|
||||
new ByteArrayInputStream(mfRawBytes));
|
||||
mfModified = !oldManifest.equals(manifest);
|
||||
if (!mfModified) {
|
||||
// leave whole manifest (mfRawBytes) unmodified
|
||||
} else {
|
||||
// reproduce the manifest raw bytes for unmodified sections
|
||||
manifest.write(baos);
|
||||
byte[] mfNewRawBytes = baos.toByteArray();
|
||||
baos.reset();
|
||||
|
||||
int newPos = findHeaderEnd(newBytes);
|
||||
int oldPos = findHeaderEnd(mfRawBytes);
|
||||
ManifestDigester oldMd = new ManifestDigester(mfRawBytes);
|
||||
ManifestDigester newMd = new ManifestDigester(mfNewRawBytes);
|
||||
|
||||
if (newPos == oldPos) {
|
||||
System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
|
||||
// main attributes
|
||||
if (manifest.getMainAttributes().equals(
|
||||
oldManifest.getMainAttributes())
|
||||
&& (manifest.getEntries().isEmpty() ||
|
||||
oldMd.getMainAttsEntry().isProperlyDelimited())) {
|
||||
oldMd.getMainAttsEntry().reproduceRaw(baos);
|
||||
} else {
|
||||
newMd.getMainAttsEntry().reproduceRaw(baos);
|
||||
}
|
||||
|
||||
// individual sections
|
||||
for (Map.Entry<String,Attributes> entry :
|
||||
manifest.getEntries().entrySet()) {
|
||||
String sectionName = entry.getKey();
|
||||
Attributes entryAtts = entry.getValue();
|
||||
if (entryAtts.equals(oldManifest.getAttributes(sectionName))
|
||||
&& oldMd.get(sectionName).isProperlyDelimited()) {
|
||||
oldMd.get(sectionName).reproduceRaw(baos);
|
||||
} else {
|
||||
// cat oldHead newTail > newBytes
|
||||
byte[] lastBytes = new byte[oldPos +
|
||||
newBytes.length - newPos];
|
||||
System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
|
||||
System.arraycopy(newBytes, newPos, lastBytes, oldPos,
|
||||
newBytes.length - newPos);
|
||||
newBytes = lastBytes;
|
||||
newMd.get(sectionName).reproduceRaw(baos);
|
||||
}
|
||||
}
|
||||
mfRawBytes = newBytes;
|
||||
} else {
|
||||
|
||||
mfRawBytes = baos.toByteArray();
|
||||
}
|
||||
}
|
||||
@ -801,13 +810,12 @@ public final class JarSigner {
|
||||
mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
|
||||
}
|
||||
if (handler != null) {
|
||||
if (mfCreated) {
|
||||
if (mfCreated || !mfModified) {
|
||||
handler.accept("adding", mfFile.getName());
|
||||
} else if (mfModified) {
|
||||
} else {
|
||||
handler.accept("updating", mfFile.getName());
|
||||
}
|
||||
}
|
||||
|
||||
zos.putNextEntry(mfFile);
|
||||
zos.write(mfRawBytes);
|
||||
|
||||
@ -826,9 +834,8 @@ public final class JarSigner {
|
||||
}
|
||||
signer.initSign(privateKey);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.reset();
|
||||
sf.write(baos);
|
||||
|
||||
byte[] content = baos.toByteArray();
|
||||
|
||||
signer.update(content);
|
||||
@ -889,6 +896,14 @@ public final class JarSigner {
|
||||
if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
|
||||
&& !ze.getName().equalsIgnoreCase(sfFilename)
|
||||
&& !ze.getName().equalsIgnoreCase(bkFilename)) {
|
||||
if (ze.getName().startsWith(SignatureFile
|
||||
.getBaseSignatureFilesName(signerName))
|
||||
&& SignatureFileVerifier.isBlockOrSF(ze.getName())) {
|
||||
if (handler != null) {
|
||||
handler.accept("updating", ze.getName());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (handler != null) {
|
||||
if (manifest.getAttributes(ze.getName()) != null) {
|
||||
handler.accept("signing", ze.getName());
|
||||
@ -942,11 +957,9 @@ public final class JarSigner {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateDigests(ZipEntry ze, ZipFile zf,
|
||||
private void updateDigests(ZipEntry ze, ZipFile zf,
|
||||
MessageDigest[] digests,
|
||||
Manifest mf) throws IOException {
|
||||
boolean update = false;
|
||||
|
||||
Attributes attrs = mf.getAttributes(ze.getName());
|
||||
String[] base64Digests = getDigests(ze, zf, digests);
|
||||
|
||||
@ -976,19 +989,9 @@ public final class JarSigner {
|
||||
|
||||
if (name == null) {
|
||||
name = digests[i].getAlgorithm() + "-Digest";
|
||||
attrs.putValue(name, base64Digests[i]);
|
||||
update = true;
|
||||
} else {
|
||||
// compare digests, and replace the one in the manifest
|
||||
// if they are different
|
||||
String mfDigest = attrs.getValue(name);
|
||||
if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
|
||||
attrs.putValue(name, base64Digests[i]);
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
attrs.putValue(name, base64Digests[i]);
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
private Attributes getDigestAttributes(
|
||||
@ -1051,30 +1054,6 @@ public final class JarSigner {
|
||||
return base64Digests;
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
private int findHeaderEnd(byte[] bs) {
|
||||
// Initial state true to deal with empty header
|
||||
boolean newline = true; // just met a newline
|
||||
int len = bs.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
switch (bs[i]) {
|
||||
case '\r':
|
||||
if (i < len - 1 && bs[i + 1] == '\n') i++;
|
||||
// fallthrough
|
||||
case '\n':
|
||||
if (newline) return i + 1; //+1 to get length
|
||||
newline = true;
|
||||
break;
|
||||
default:
|
||||
newline = false;
|
||||
}
|
||||
}
|
||||
// If header end is not found, it means the MANIFEST.MF has only
|
||||
// the main attributes section and it does not end with 2 newlines.
|
||||
// Returns the whole length so that it can be completely replaced.
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to load the specified signing mechanism.
|
||||
* The URL class loader is used.
|
||||
@ -1145,14 +1124,12 @@ public final class JarSigner {
|
||||
}
|
||||
|
||||
// create digest of the manifest main attributes
|
||||
ManifestDigester.Entry mde =
|
||||
md.get(ManifestDigester.MF_MAIN_ATTRS, false);
|
||||
ManifestDigester.Entry mde = md.getMainAttsEntry(false);
|
||||
if (mde != null) {
|
||||
for (MessageDigest digest: digests) {
|
||||
mattr.putValue(digest.getAlgorithm() +
|
||||
"-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
|
||||
Base64.getEncoder().encodeToString(
|
||||
mde.digest(digest)));
|
||||
for (MessageDigest digest : digests) {
|
||||
mattr.putValue(digest.getAlgorithm() + "-Digest-" +
|
||||
ManifestDigester.MF_MAIN_ATTRS,
|
||||
Base64.getEncoder().encodeToString(mde.digest(digest)));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException
|
||||
@ -1181,15 +1158,19 @@ public final class JarSigner {
|
||||
sf.write(out);
|
||||
}
|
||||
|
||||
private static String getBaseSignatureFilesName(String baseName) {
|
||||
return "META-INF/" + baseName + ".";
|
||||
}
|
||||
|
||||
// get .SF file name
|
||||
public String getMetaName() {
|
||||
return "META-INF/" + baseName + ".SF";
|
||||
return getBaseSignatureFilesName(baseName) + "SF";
|
||||
}
|
||||
|
||||
// get .DSA (or .DSA, .EC) file name
|
||||
public String getBlockName(PrivateKey privateKey) {
|
||||
String keyAlgorithm = privateKey.getAlgorithm();
|
||||
return "META-INF/" + baseName + "." + keyAlgorithm;
|
||||
return getBaseSignatureFilesName(baseName) + keyAlgorithm;
|
||||
}
|
||||
|
||||
// Generates the PKCS#7 content of block file
|
||||
|
@ -944,11 +944,10 @@ public class Main {
|
||||
// Don't read from the newManifest InputStream, as we
|
||||
// might need it below, and we can't re-read the same data
|
||||
// twice.
|
||||
FileInputStream fis = new FileInputStream(mname);
|
||||
boolean ambiguous = isAmbiguousMainClass(new Manifest(fis));
|
||||
fis.close();
|
||||
if (ambiguous) {
|
||||
return false;
|
||||
try (FileInputStream fis = new FileInputStream(mname)) {
|
||||
if (isAmbiguousMainClass(new Manifest(fis))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the manifest.
|
||||
|
@ -1,45 +0,0 @@
|
||||
## Pako v1.0
|
||||
|
||||
### Pako License
|
||||
<pre>
|
||||
Copyright (C) 2014-2017 by Vitaly Puzrin and Andrei Tuputcyn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
(C) 1995-2013 Jean-loup Gailly and Mark Adler
|
||||
(C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
</pre>
|
||||
|
||||
|
@ -163,7 +163,6 @@ public class SharedArchiveConsistency {
|
||||
public static void writeData(FileChannel fc, long offset, ByteBuffer bb) throws Exception {
|
||||
fc.position(offset);
|
||||
fc.write(bb);
|
||||
fc.force(true);
|
||||
}
|
||||
|
||||
public static FileChannel getFileChannel(File jsaFile) throws Exception {
|
||||
@ -247,7 +246,6 @@ public class SharedArchiveConsistency {
|
||||
bbuf.clear();
|
||||
bytes_written += 4096;
|
||||
}
|
||||
fc.force(true);
|
||||
if (fc.isOpen()) {
|
||||
fc.close();
|
||||
}
|
||||
|
@ -655,13 +655,13 @@ com/sun/nio/sctp/SctpChannel/SocketOptionTests.java 8141694 linux-al
|
||||
sun/security/pkcs11/ec/TestKeyFactory.java 8026976 generic-all
|
||||
sun/security/pkcs11/Secmod/AddTrustedCert.java 8180837 generic-all
|
||||
sun/security/pkcs11/tls/TestKeyMaterial.java 8180837 generic-all
|
||||
sun/security/pkcs11/tls/tls12/FipsModeTLS12.java 8224954,8225678 windows-all,linux-all
|
||||
sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java 8161536 generic-all
|
||||
|
||||
sun/security/tools/keytool/ListKeychainStore.sh 8156889 macosx-all
|
||||
sun/security/tools/keytool/KeyToolTest.java 8224644 solaris-all
|
||||
sun/security/tools/keytool/WeakAlg.java 8224644 solaris-all
|
||||
|
||||
sun/security/tools/jarsigner/compatibility/SignTwice.java 8228341 windows-all
|
||||
sun/security/tools/jarsigner/warnings/BadKeyUsageTest.java 8026393 generic-all
|
||||
|
||||
javax/net/ssl/ServerName/SSLEngineExplorerMatchedSNI.java 8212096 generic-all
|
||||
@ -676,6 +676,28 @@ sun/security/pkcs11/KeyStore/SecretKeysBasic.sh 8209398 generic-
|
||||
|
||||
security/infra/java/security/cert/CertPathValidator/certification/ActalisCA.java 8224768 generic-all
|
||||
|
||||
sun/security/smartcardio/TestChannel.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestConnect.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestConnectAgain.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestControl.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestDefault.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestDirect.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestExclusive.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestMultiplePresent.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestPresent.java 8039280 generic-all
|
||||
sun/security/smartcardio/TestTransmit.java 8039280 generic-all
|
||||
com/sun/crypto/provider/Cipher/DES/PerformanceTest.java 8039280 generic-all
|
||||
com/sun/security/auth/callback/TextCallbackHandler/Default.java 8039280 generic-all
|
||||
com/sun/security/auth/callback/TextCallbackHandler/Password.java 8039280 generic-all
|
||||
com/sun/security/sasl/gsskerb/AuthOnly.java 8039280 generic-all
|
||||
com/sun/security/sasl/gsskerb/ConfSecurityLayer.java 8039280 generic-all
|
||||
com/sun/security/sasl/gsskerb/NoSecurityLayer.java 8039280 generic-all
|
||||
javax/security/auth/kerberos/KerberosHashEqualsTest.java 8039280 generic-all
|
||||
javax/security/auth/kerberos/KerberosTixDateTest.java 8039280 generic-all
|
||||
sun/security/provider/PolicyFile/GrantAllPermToExtWhenNoPolicy.java 8039280 generic-all
|
||||
sun/security/provider/PolicyParser/ExtDirsChange.java 8039280 generic-all
|
||||
sun/security/provider/PolicyParser/PrincipalExpansionError.java 8039280 generic-all
|
||||
|
||||
############################################################################
|
||||
|
||||
# jdk_sound
|
||||
|
@ -22,23 +22,21 @@
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import jdk.test.lib.thread.ProcessThread;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 6425769
|
||||
* @summary Test JMX agent host address binding. Same ports but different
|
||||
* interfaces to bind to (selecting plain or SSL sockets at random
|
||||
* @key intermittent
|
||||
* interfaces to bind to (selecting plain or SSL sockets at random)
|
||||
*
|
||||
* @library /test/lib
|
||||
* @modules java.management.rmi
|
||||
@ -52,14 +50,9 @@ public class JMXInterfaceBindingTest {
|
||||
public static final int STOP_PROCESS_EXIT_VAL = 10;
|
||||
public static final int JMX_PORT_RANGE_LOWER = 9100;
|
||||
public static final int JMX_PORT_RANGE_UPPER = 9200;
|
||||
public static final int JMX_PORT = getRandomPortInRange(JMX_PORT_RANGE_LOWER,
|
||||
JMX_PORT_RANGE_UPPER);
|
||||
public static final int JMX_PORT_RANGE_LOWER_SSL = 9201; // 9200 might be RMI Port
|
||||
public static final int JMX_PORT_RANGE_UPPER_SSL = 9300;
|
||||
public static final int JMX_PORT_SSL = getRandomPortInRange(JMX_PORT_RANGE_LOWER_SSL,
|
||||
JMX_PORT_RANGE_UPPER_SSL);
|
||||
public static final int RMI_PORT = JMX_PORT + 1;
|
||||
public static final int RMI_PORT_SSL = JMX_PORT_SSL + 1;
|
||||
private static final int MAX_RETRY_ATTEMTS = 10;
|
||||
public static final String READY_MSG = "MainThread: Ready for connections";
|
||||
public static final String TEST_CLASS = JMXAgentInterfaceBinding.class.getSimpleName();
|
||||
public static final String KEYSTORE_LOC = System.getProperty("test.src", ".") +
|
||||
@ -82,98 +75,25 @@ public class JMXInterfaceBindingTest {
|
||||
}
|
||||
|
||||
private void runTests(List<InetAddress> addrs, boolean useSSL) {
|
||||
List<ProcessThread> jvms = new ArrayList<>(addrs.size());
|
||||
int i = 1;
|
||||
List<TestProcessThread> testThreads = new ArrayList<>(addrs.size());
|
||||
CountDownLatch latch = new CountDownLatch(addrs.size());
|
||||
for (InetAddress addr : addrs) {
|
||||
String address = JMXAgentInterfaceBinding.wrapAddress(addr.getHostAddress());
|
||||
System.out.println();
|
||||
String msg = String.format("DEBUG: Launching java tester for triplet (HOSTNAME,JMX_PORT,RMI_PORT) == (%s,%d,%d)",
|
||||
address,
|
||||
useSSL ? JMX_PORT_SSL : JMX_PORT,
|
||||
useSSL ? RMI_PORT_SSL : RMI_PORT);
|
||||
System.out.println(msg);
|
||||
ProcessThread jvm = runJMXBindingTest(address, useSSL);
|
||||
jvms.add(jvm);
|
||||
jvm.start();
|
||||
System.out.println("DEBUG: Started " + (i++) + " Process(es).");
|
||||
TestProcessThread t = new TestProcessThread(address, useSSL, latch);
|
||||
testThreads.add(t);
|
||||
t.start();
|
||||
}
|
||||
int failedProcesses = 0;
|
||||
for (ProcessThread pt: jvms) {
|
||||
try {
|
||||
pt.sendMessage("Exit: " + STOP_PROCESS_EXIT_VAL);
|
||||
pt.join();
|
||||
} catch (Throwable e) {
|
||||
System.err.println("Failed to stop process: " + pt.getName());
|
||||
throw new RuntimeException("Test failed", e);
|
||||
}
|
||||
int exitValue = pt.getOutput().getExitValue();
|
||||
// If there is a communication error (the case we care about)
|
||||
// we get a exit code of 1
|
||||
if (exitValue == COMMUNICATION_ERROR_EXIT_VAL) {
|
||||
// Failure case since the java processes should still be
|
||||
// running.
|
||||
System.err.println("Test FAILURE on " + pt.getName());
|
||||
failedProcesses++;
|
||||
} else if (exitValue == STOP_PROCESS_EXIT_VAL) {
|
||||
System.out.println("DEBUG: OK. Spawned java process terminated with expected exit code of " + STOP_PROCESS_EXIT_VAL);
|
||||
} else {
|
||||
System.err.println("Test FAILURE on " + pt.getName() + " reason: Unexpected exit code => " + exitValue);
|
||||
failedProcesses++;
|
||||
}
|
||||
}
|
||||
if (failedProcesses > 0) {
|
||||
throw new RuntimeException("Test FAILED. " + failedProcesses + " out of " + addrs.size() + " process(es) failed to start the JMX agent.");
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessThread runJMXBindingTest(String address, boolean useSSL) {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("-classpath");
|
||||
args.add(TEST_CLASSPATH);
|
||||
args.add("-Dcom.sun.management.jmxremote.host=" + address);
|
||||
args.add("-Dcom.sun.management.jmxremote.port=" + (useSSL ? JMX_PORT_SSL : JMX_PORT));
|
||||
args.add("-Dcom.sun.management.jmxremote.rmi.port=" + (useSSL ? RMI_PORT_SSL : RMI_PORT));
|
||||
args.add("-Dcom.sun.management.jmxremote.authenticate=false");
|
||||
args.add("-Dcom.sun.management.jmxremote.ssl=" + Boolean.toString(useSSL));
|
||||
// This is needed for testing on loopback
|
||||
args.add("-Djava.rmi.server.hostname=" + address);
|
||||
if (useSSL) {
|
||||
args.add("-Dcom.sun.management.jmxremote.registry.ssl=true");
|
||||
args.add("-Djavax.net.ssl.keyStore=" + KEYSTORE_LOC);
|
||||
args.add("-Djavax.net.ssl.trustStore=" + TRUSTSTORE_LOC);
|
||||
args.add("-Djavax.net.ssl.keyStorePassword=password");
|
||||
args.add("-Djavax.net.ssl.trustStorePassword=trustword");
|
||||
}
|
||||
args.add(TEST_CLASS);
|
||||
args.add(address);
|
||||
args.add(Integer.toString(useSSL ? JMX_PORT_SSL : JMX_PORT));
|
||||
args.add(Integer.toString(useSSL ? RMI_PORT_SSL : RMI_PORT));
|
||||
args.add(Boolean.toString(useSSL));
|
||||
try {
|
||||
ProcessBuilder builder = ProcessTools.createJavaProcessBuilder(args.toArray(new String[] {}));
|
||||
System.out.println(ProcessTools.getCommandLine(builder));
|
||||
ProcessThread jvm = new ProcessThread("JMX-Tester-" + address, JMXInterfaceBindingTest::isJMXAgentResponseAvailable, builder);
|
||||
return jvm;
|
||||
} catch (Exception e) {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Failed to wait for the test threads to complete");
|
||||
throw new RuntimeException("Test failed", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static boolean isJMXAgentResponseAvailable(String line) {
|
||||
if (line.equals(READY_MSG)) {
|
||||
System.out.println("DEBUG: Found expected READY_MSG.");
|
||||
return true;
|
||||
} else if (line.startsWith("Error:")) {
|
||||
// Allow for a JVM process that exits with
|
||||
// "Error: JMX connector server communication error: ..."
|
||||
// to continue as well since we handle that case elsewhere.
|
||||
// This has the effect that the test does not timeout and
|
||||
// fails with an exception in the test.
|
||||
System.err.println("PROBLEM: JMX agent of target JVM did not start as it should.");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
long failedProcesses = testThreads.stream().filter(TestProcessThread::isTestFailed).count();
|
||||
if (failedProcesses > 0) {
|
||||
throw new RuntimeException("Test FAILED. " + failedProcesses + " out of " + addrs.size() +
|
||||
" process(es) failed to start the JMX agent.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,4 +135,128 @@ public class JMXInterfaceBindingTest {
|
||||
throw new RuntimeException("Test failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestProcessThread extends Thread {
|
||||
private final String name;
|
||||
private final String address;
|
||||
private final boolean useSSL;
|
||||
private final CountDownLatch latch;
|
||||
private volatile boolean testFailed = false;
|
||||
private OutputAnalyzer output;
|
||||
|
||||
public TestProcessThread(String address, boolean useSSL, CountDownLatch latch) {
|
||||
this.address = address;
|
||||
this.useSSL = useSSL;
|
||||
this.name = "JMX-Tester-" + address;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
int attempts = 0;
|
||||
boolean needRetry = false;
|
||||
do {
|
||||
if (needRetry) {
|
||||
System.err.println("Retrying the test for " + name);
|
||||
}
|
||||
needRetry = runTest();
|
||||
} while (needRetry && (attempts++ < MAX_RETRY_ATTEMTS));
|
||||
|
||||
if (testFailed) {
|
||||
int exitValue = output.getExitValue();
|
||||
if (needRetry) {
|
||||
System.err.println("Test FAILURE on " + name + " reason: run out of retries to to pick free ports");
|
||||
} else if (exitValue == COMMUNICATION_ERROR_EXIT_VAL) {
|
||||
// Failure case since the java processes should still be
|
||||
// running.
|
||||
System.err.println("Test FAILURE on " + name);
|
||||
} else if (exitValue == STOP_PROCESS_EXIT_VAL) {
|
||||
System.out.println("Test FAILURE on " + name + " reason: The expected line \"" + READY_MSG
|
||||
+ "\" is not present in the process output");
|
||||
} else {
|
||||
System.err.println("Test FAILURE on " + name + " reason: Unexpected exit code => " + exitValue);
|
||||
}
|
||||
output.reportDiagnosticSummary();
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public boolean isTestFailed() {
|
||||
return testFailed;
|
||||
}
|
||||
|
||||
private int getJMXPort() {
|
||||
return useSSL ?
|
||||
getRandomPortInRange(JMX_PORT_RANGE_LOWER_SSL, JMX_PORT_RANGE_UPPER_SSL) :
|
||||
getRandomPortInRange(JMX_PORT_RANGE_LOWER, JMX_PORT_RANGE_UPPER);
|
||||
}
|
||||
|
||||
private Process createTestProcess() {
|
||||
int jmxPort = getJMXPort();
|
||||
int rmiPort = jmxPort + 1;
|
||||
String msg = String.format("DEBUG: Launching java tester for triplet (HOSTNAME,JMX_PORT,RMI_PORT)" +
|
||||
" == (%s,%d,%d)", address, jmxPort, rmiPort);
|
||||
System.out.println(msg);
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add("-classpath");
|
||||
args.add(TEST_CLASSPATH);
|
||||
args.add("-Dcom.sun.management.jmxremote.host=" + address);
|
||||
args.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
|
||||
args.add("-Dcom.sun.management.jmxremote.rmi.port=" + rmiPort);
|
||||
args.add("-Dcom.sun.management.jmxremote.authenticate=false");
|
||||
args.add("-Dcom.sun.management.jmxremote.ssl=" + Boolean.toString(useSSL));
|
||||
// This is needed for testing on loopback
|
||||
args.add("-Djava.rmi.server.hostname=" + address);
|
||||
if (useSSL) {
|
||||
args.add("-Dcom.sun.management.jmxremote.registry.ssl=true");
|
||||
args.add("-Djavax.net.ssl.keyStore=" + KEYSTORE_LOC);
|
||||
args.add("-Djavax.net.ssl.trustStore=" + TRUSTSTORE_LOC);
|
||||
args.add("-Djavax.net.ssl.keyStorePassword=password");
|
||||
args.add("-Djavax.net.ssl.trustStorePassword=trustword");
|
||||
}
|
||||
args.add(TEST_CLASS);
|
||||
args.add(address);
|
||||
args.add(Integer.toString(jmxPort));
|
||||
args.add(Integer.toString(rmiPort));
|
||||
args.add(Boolean.toString(useSSL));
|
||||
|
||||
try {
|
||||
ProcessBuilder builder = ProcessTools.createJavaProcessBuilder(args.toArray(new String[]{}));
|
||||
System.out.println(ProcessTools.getCommandLine(builder));
|
||||
Process process = builder.start();
|
||||
output = new OutputAnalyzer(process);
|
||||
return process;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Test failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the test failed due to "Port already in use" error.
|
||||
private boolean runTest() {
|
||||
testFailed = true;
|
||||
Process process = createTestProcess();
|
||||
try {
|
||||
sendMessageToProcess(process, "Exit: " + STOP_PROCESS_EXIT_VAL);
|
||||
process.waitFor();
|
||||
} catch (Throwable e) {
|
||||
System.err.println("Failed to stop process: " + name);
|
||||
throw new RuntimeException("Test failed", e);
|
||||
}
|
||||
if (output.getExitValue() == STOP_PROCESS_EXIT_VAL && output.getStdout().contains(READY_MSG)) {
|
||||
testFailed = false;
|
||||
} else if (output.getStderr().contains("Port already in use")) {
|
||||
System.out.println("The test attempt for the test " + name +" failed due to the bind error");
|
||||
// Need to retry
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void sendMessageToProcess(Process process, String message) {
|
||||
try (PrintWriter pw = new PrintWriter(process.getOutputStream())) {
|
||||
pw.println(message);
|
||||
pw.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -808,8 +808,10 @@ public class KDC {
|
||||
|
||||
PrincipalName cname = null;
|
||||
boolean allowForwardable = true;
|
||||
|
||||
boolean isReferral = false;
|
||||
if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) {
|
||||
System.out.println(realm + "> verifying referral for " +
|
||||
body.sname.getNameString());
|
||||
KDC referral = aliasReferrals.get(body.sname.getNameString());
|
||||
if (referral != null) {
|
||||
service = new PrincipalName(
|
||||
@ -817,6 +819,9 @@ public class KDC {
|
||||
PrincipalName.NAME_COMPONENT_SEPARATOR_STR +
|
||||
referral.getRealm(), PrincipalName.KRB_NT_SRV_INST,
|
||||
this.getRealm());
|
||||
System.out.println(realm + "> referral to " +
|
||||
referral.getRealm());
|
||||
isReferral = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -918,7 +923,8 @@ public class KDC {
|
||||
if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) {
|
||||
bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true;
|
||||
}
|
||||
if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT)) {
|
||||
if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT) &&
|
||||
!isReferral) {
|
||||
if (!options.containsKey(Option.ALLOW_S4U2PROXY)) {
|
||||
// Don't understand CNAME_IN_ADDL_TKT
|
||||
throw new KrbException(Krb5.KDC_ERR_BADOPTION);
|
||||
@ -1074,8 +1080,7 @@ public class KDC {
|
||||
}
|
||||
int eType = eTypes[0];
|
||||
|
||||
if (body.kdcOptions.get(KDCOptions.CANONICALIZE) &&
|
||||
body.cname.getNameType() == PrincipalName.KRB_NT_ENTERPRISE) {
|
||||
if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) {
|
||||
PrincipalName principal = alias2Principals.get(
|
||||
body.cname.getNameString());
|
||||
if (principal != null) {
|
||||
|
@ -30,9 +30,18 @@
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import sun.security.krb5.Credentials;
|
||||
import sun.security.krb5.internal.CredentialsUtil;
|
||||
import sun.security.krb5.KrbAsReqBuilder;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.security.auth.kerberos.KerberosTicket;
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
import org.ietf.jgss.GSSName;
|
||||
|
||||
import sun.security.jgss.GSSUtil;
|
||||
import sun.security.krb5.PrincipalName;
|
||||
|
||||
public class ReferralsTest {
|
||||
@ -41,39 +50,32 @@ public class ReferralsTest {
|
||||
private static final String realmKDC1 = "RABBIT.HOLE";
|
||||
private static final String realmKDC2 = "DEV.RABBIT.HOLE";
|
||||
private static final char[] password = "123qwe@Z".toCharArray();
|
||||
|
||||
// Names
|
||||
private static final String clientName = "test";
|
||||
|
||||
private static final String clientAlias = clientName +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1;
|
||||
|
||||
private static final String clientKDC1QueryName = clientAlias.replaceAll(
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR, "\\\\" +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR) +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1;
|
||||
private static PrincipalName clientKDC1QueryPrincipal = null;
|
||||
static {
|
||||
try {
|
||||
clientKDC1QueryPrincipal = new PrincipalName(
|
||||
clientKDC1QueryName, PrincipalName.KRB_NT_ENTERPRISE,
|
||||
null);
|
||||
} catch (Throwable t) {}
|
||||
}
|
||||
|
||||
private static final String clientKDC2Name = clientName +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2;
|
||||
|
||||
private static final String serviceName = "http" +
|
||||
PrincipalName.NAME_COMPONENT_SEPARATOR_STR +
|
||||
"server.dev.rabbit.hole";
|
||||
|
||||
private static Credentials tgt;
|
||||
private static Credentials tgs;
|
||||
// Alias
|
||||
private static final String clientAlias = clientName +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1;
|
||||
|
||||
// Names + realms
|
||||
private static final String clientKDC1Name = clientAlias.replaceAll(
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR, "\\\\" +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR) +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1;
|
||||
private static final String clientKDC2Name = clientName +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2;
|
||||
private static final String serviceKDC2Name = serviceName +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try {
|
||||
initializeKDCs();
|
||||
getTGT();
|
||||
getTGS();
|
||||
testSubjectCredentials();
|
||||
testDelegated();
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
@ -108,6 +110,11 @@ public class ReferralsTest {
|
||||
kdc1.registerAlias(serviceName, kdc2);
|
||||
kdc2.registerAlias(clientAlias, clientKDC2Name);
|
||||
|
||||
Map<String,List<String>> mapKDC2 = new HashMap<>();
|
||||
mapKDC2.put(serviceName + "@" + realmKDC2, Arrays.asList(
|
||||
new String[]{serviceName + "@" + realmKDC2}));
|
||||
kdc2.setOption(KDC.Option.ALLOW_S4U2PROXY, mapKDC2);
|
||||
|
||||
KDC.saveConfig(krbConfigName, kdc1, kdc2,
|
||||
"forwardable=true");
|
||||
System.setProperty("java.security.krb5.conf", krbConfigName);
|
||||
@ -120,50 +127,123 @@ public class ReferralsTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void getTGT() throws Exception {
|
||||
KrbAsReqBuilder builder = new KrbAsReqBuilder(clientKDC1QueryPrincipal,
|
||||
password);
|
||||
tgt = builder.action().getCreds();
|
||||
builder.destroy();
|
||||
/*
|
||||
* The client subject (whose principal is
|
||||
* test@RABBIT.HOLE@RABBIT.HOLE) will obtain a TGT after
|
||||
* realm referral and name canonicalization (TGT cname
|
||||
* will be test@DEV.RABBIT.HOLE). With this TGT, the client will request
|
||||
* a TGS for service http/server.dev.rabbit.hole@RABBIT.HOLE. After
|
||||
* realm referral, a http/server.dev.rabbit.hole@DEV.RABBIT.HOLE TGS
|
||||
* will be obtained.
|
||||
*
|
||||
* Assert that we get the proper TGT and TGS tickets, and that they are
|
||||
* associated to the client subject.
|
||||
*
|
||||
* Assert that if we request a TGS for the same service again (based on the
|
||||
* original service name), we don't get a new one but the previous,
|
||||
* already in the subject credentials.
|
||||
*/
|
||||
private static void testSubjectCredentials() throws Exception {
|
||||
Subject clientSubject = new Subject();
|
||||
Context clientContext = Context.fromUserPass(clientSubject,
|
||||
clientKDC1Name, password, false);
|
||||
|
||||
Set<Principal> clientPrincipals = clientSubject.getPrincipals();
|
||||
if (clientPrincipals.size() != 1) {
|
||||
throw new Exception("Only one client subject principal expected");
|
||||
}
|
||||
Principal clientPrincipal = clientPrincipals.iterator().next();
|
||||
if (DEBUG) {
|
||||
System.out.println("TGT");
|
||||
System.out.println("----------------------");
|
||||
System.out.println(tgt);
|
||||
System.out.println("----------------------");
|
||||
System.out.println("Client subject principal: " +
|
||||
clientPrincipal.getName());
|
||||
}
|
||||
if (tgt == null) {
|
||||
throw new Exception("TGT is null");
|
||||
if (!clientPrincipal.getName().equals(clientKDC1Name)) {
|
||||
throw new Exception("Unexpected client subject principal.");
|
||||
}
|
||||
if (!tgt.getClient().getName().equals(clientKDC2Name)) {
|
||||
throw new Exception("Unexpected TGT client");
|
||||
|
||||
clientContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID);
|
||||
clientContext.take(new byte[0]);
|
||||
Set<KerberosTicket> clientTickets =
|
||||
clientSubject.getPrivateCredentials(KerberosTicket.class);
|
||||
boolean tgtFound = false;
|
||||
boolean tgsFound = false;
|
||||
for (KerberosTicket clientTicket : clientTickets) {
|
||||
String cname = clientTicket.getClient().getName();
|
||||
String sname = clientTicket.getServer().getName();
|
||||
if (cname.equals(clientKDC2Name)) {
|
||||
if (sname.equals(PrincipalName.TGS_DEFAULT_SRV_NAME +
|
||||
PrincipalName.NAME_COMPONENT_SEPARATOR_STR +
|
||||
realmKDC2 + PrincipalName.NAME_REALM_SEPARATOR_STR +
|
||||
realmKDC2)) {
|
||||
tgtFound = true;
|
||||
} else if (sname.equals(serviceKDC2Name)) {
|
||||
tgsFound = true;
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
System.out.println("Client subject KerberosTicket:");
|
||||
System.out.println(clientTicket);
|
||||
}
|
||||
}
|
||||
String[] tgtServerNames = tgt.getServer().getNameStrings();
|
||||
if (tgtServerNames.length != 2 || !tgtServerNames[0].equals(
|
||||
PrincipalName.TGS_DEFAULT_SRV_NAME) ||
|
||||
!tgtServerNames[1].equals(realmKDC2) ||
|
||||
!tgt.getServer().getRealmString().equals(realmKDC2)) {
|
||||
throw new Exception("Unexpected TGT server");
|
||||
if (!tgtFound || !tgsFound) {
|
||||
throw new Exception("client subject tickets (TGT/TGS) not found.");
|
||||
}
|
||||
int numOfTickets = clientTickets.size();
|
||||
clientContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID);
|
||||
clientContext.take(new byte[0]);
|
||||
clientContext.status();
|
||||
int newNumOfTickets =
|
||||
clientSubject.getPrivateCredentials(KerberosTicket.class).size();
|
||||
if (DEBUG) {
|
||||
System.out.println("client subject number of tickets: " +
|
||||
numOfTickets);
|
||||
System.out.println("client subject new number of tickets: " +
|
||||
newNumOfTickets);
|
||||
}
|
||||
if (numOfTickets != newNumOfTickets) {
|
||||
throw new Exception("Useless client subject TGS request because" +
|
||||
" TGS was not found in private credentials.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void getTGS() throws Exception {
|
||||
tgs = CredentialsUtil.acquireServiceCreds(serviceName +
|
||||
PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1, tgt);
|
||||
/*
|
||||
* The server (http/server.dev.rabbit.hole@DEV.RABBIT.HOLE)
|
||||
* will authenticate on itself on behalf of the client
|
||||
* (test@DEV.RABBIT.HOLE). Cross-realm referrals will occur
|
||||
* when requesting different TGTs and TGSs (including the
|
||||
* request for delegated credentials).
|
||||
*/
|
||||
private static void testDelegated() throws Exception {
|
||||
Context c = Context.fromUserPass(clientKDC2Name,
|
||||
password, false);
|
||||
c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID);
|
||||
Context s = Context.fromUserPass(serviceKDC2Name,
|
||||
password, true);
|
||||
s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
|
||||
Context.handshake(c, s);
|
||||
Context delegatedContext = s.delegated();
|
||||
delegatedContext.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID);
|
||||
delegatedContext.x().requestMutualAuth(false);
|
||||
Context s2 = Context.fromUserPass(serviceKDC2Name,
|
||||
password, true);
|
||||
s2.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
|
||||
|
||||
// Test authentication
|
||||
Context.handshake(delegatedContext, s2);
|
||||
if (!delegatedContext.x().isEstablished() || !s2.x().isEstablished()) {
|
||||
throw new Exception("Delegated authentication failed");
|
||||
}
|
||||
|
||||
// Test identities
|
||||
GSSName contextInitiatorName = delegatedContext.x().getSrcName();
|
||||
GSSName contextAcceptorName = delegatedContext.x().getTargName();
|
||||
if (DEBUG) {
|
||||
System.out.println("TGS");
|
||||
System.out.println("----------------------");
|
||||
System.out.println(tgs);
|
||||
System.out.println("----------------------");
|
||||
System.out.println("Context initiator: " + contextInitiatorName);
|
||||
System.out.println("Context acceptor: " + contextAcceptorName);
|
||||
}
|
||||
if (tgs == null) {
|
||||
throw new Exception("TGS is null");
|
||||
}
|
||||
if (!tgs.getClient().getName().equals(clientKDC2Name)) {
|
||||
throw new Exception("Unexpected TGS client");
|
||||
}
|
||||
if (!tgs.getServer().getNameString().equals(serviceName) ||
|
||||
!tgs.getServer().getRealmString().equals(realmKDC2)) {
|
||||
throw new Exception("Unexpected TGS server");
|
||||
if (!contextInitiatorName.toString().equals(clientKDC2Name) ||
|
||||
!contextAcceptorName.toString().equals(serviceName)) {
|
||||
throw new Exception("Unexpected initiator or acceptor names");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @library /test/lib
|
||||
* @summary Test that a New Session Ticket will be generated when a
|
||||
* SSLSessionBindingListener is set (boundValues)
|
||||
* @run main/othervm ResumptionUpdateBoundValues
|
||||
*/
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSessionBindingEvent;
|
||||
import javax.net.ssl.SSLSessionBindingListener;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import jdk.test.lib.Utils;
|
||||
|
||||
public class ResumptionUpdateBoundValues {
|
||||
|
||||
static boolean separateServerThread = true;
|
||||
|
||||
/*
|
||||
* Where do we find the keystores?
|
||||
*/
|
||||
static String pathToStores = "../../../../javax/net/ssl/etc/";
|
||||
static String keyStoreFile = "keystore";
|
||||
static String trustStoreFile = "truststore";
|
||||
static String passwd = "passphrase";
|
||||
|
||||
/*
|
||||
* Is the server ready to serve?
|
||||
*/
|
||||
volatile static boolean serverReady = false;
|
||||
|
||||
/*
|
||||
* Turn on SSL debugging?
|
||||
*/
|
||||
static boolean debug = false;
|
||||
|
||||
/*
|
||||
* Define the server side of the test.
|
||||
*
|
||||
* If the server prematurely exits, serverReady will be set to true
|
||||
* to avoid infinite hangs.
|
||||
*/
|
||||
void doServerSide() throws Exception {
|
||||
SSLServerSocketFactory sslssf =
|
||||
(SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
|
||||
SSLServerSocket sslServerSocket =
|
||||
(SSLServerSocket) sslssf.createServerSocket(serverPort);
|
||||
serverPort = sslServerSocket.getLocalPort();
|
||||
|
||||
/*
|
||||
* Signal Client, we're ready for his connect.
|
||||
*/
|
||||
serverReady = true;
|
||||
|
||||
while (serverReady) {
|
||||
SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
|
||||
InputStream sslIS = sslSocket.getInputStream();
|
||||
OutputStream sslOS = sslSocket.getOutputStream();
|
||||
|
||||
sslIS.read();
|
||||
sslOS.write(85);
|
||||
sslOS.flush();
|
||||
SSLSession sslSession = sslSocket.getSession();
|
||||
SBListener sbListener = new SBListener(sslSession);
|
||||
sslSession.putValue("x", sbListener);
|
||||
|
||||
sslIS.read();
|
||||
sslOS.write(85);
|
||||
sslOS.flush();
|
||||
|
||||
sslSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Define the client side of the test.
|
||||
*
|
||||
* If the server prematurely exits, serverReady will be set to true
|
||||
* to avoid infinite hangs.
|
||||
*/
|
||||
SBListener doClientSide() throws Exception {
|
||||
|
||||
/*
|
||||
* Wait for server to get started.
|
||||
*/
|
||||
while (!serverReady) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
|
||||
SSLSocketFactory sslsf =
|
||||
(SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
|
||||
try {
|
||||
SSLSocket sslSocket = (SSLSocket)
|
||||
sslsf.createSocket("localhost", serverPort);
|
||||
InputStream sslIS = sslSocket.getInputStream();
|
||||
OutputStream sslOS = sslSocket.getOutputStream();
|
||||
|
||||
sslOS.write(280);
|
||||
sslOS.flush();
|
||||
sslIS.read();
|
||||
|
||||
SSLSession sslSession = sslSocket.getSession();
|
||||
System.out.printf(" sslSession: %s %n %s%n", sslSession, sslSession.getClass());
|
||||
SBListener sbListener = new SBListener(sslSession);
|
||||
|
||||
sslOS.write(280);
|
||||
sslOS.flush();
|
||||
sslIS.read();
|
||||
|
||||
sslOS.write(280);
|
||||
sslOS.flush();
|
||||
sslIS.read();
|
||||
|
||||
sslOS.close();
|
||||
sslIS.close();
|
||||
sslSocket.close();
|
||||
|
||||
sslOS = null;
|
||||
sslIS = null;
|
||||
sslSession = null;
|
||||
sslSocket = null;
|
||||
Reference.reachabilityFence(sslOS);
|
||||
Reference.reachabilityFence(sslIS);
|
||||
Reference.reachabilityFence(sslSession);
|
||||
Reference.reachabilityFence(sslSocket);
|
||||
|
||||
return sbListener;
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* =============================================================
|
||||
* The remainder is just support stuff
|
||||
*/
|
||||
|
||||
// use any free port by default
|
||||
volatile int serverPort = 0;
|
||||
|
||||
volatile Exception serverException = null;
|
||||
volatile Exception clientException = null;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
if (args.length == 0) {
|
||||
System.setProperty("test.java.opts",
|
||||
"-Dtest.src=" + System.getProperty("test.src") +
|
||||
" -Dtest.jdk=" + System.getProperty("test.jdk") +
|
||||
" -Djavax.net.debug=ssl,handshake");
|
||||
|
||||
System.out.println("test.java.opts: " +
|
||||
System.getProperty("test.java.opts"));
|
||||
|
||||
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true,
|
||||
Utils.addTestJavaOpts("ResumptionUpdateBoundValues", "p"));
|
||||
|
||||
OutputAnalyzer output = ProcessTools.executeProcess(pb);
|
||||
try {
|
||||
output.shouldContain("trigger new session ticket");
|
||||
System.out.println("Found NST in debugging");
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
} finally {
|
||||
System.out.println("-- BEGIN Stdout:");
|
||||
System.out.println(output.getStdout());
|
||||
System.out.println("-- END Stdout");
|
||||
System.out.println("-- BEGIN Stderr:");
|
||||
System.out.println(output.getStderr());
|
||||
System.out.println("-- END Stderr");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String keyFilename =
|
||||
System.getProperty("test.src", "./") + "/" + pathToStores +
|
||||
"/" + keyStoreFile;
|
||||
String trustFilename =
|
||||
System.getProperty("test.src", "./") + "/" + pathToStores +
|
||||
"/" + trustStoreFile;
|
||||
System.setProperty("javax.net.ssl.keyStore", keyFilename);
|
||||
System.setProperty("javax.net.ssl.keyStorePassword", passwd);
|
||||
System.setProperty("javax.net.ssl.trustStore", trustFilename);
|
||||
System.setProperty("javax.net.ssl.trustStorePassword", passwd);
|
||||
|
||||
if (debug)
|
||||
System.setProperty("javax.net.debug", "all");
|
||||
|
||||
/*
|
||||
* Start the tests.
|
||||
*/
|
||||
|
||||
new ResumptionUpdateBoundValues();
|
||||
}
|
||||
|
||||
ArrayBlockingQueue<Thread> threads = new ArrayBlockingQueue<Thread>(100);
|
||||
|
||||
ArrayBlockingQueue<SBListener> sbListeners = new ArrayBlockingQueue<>(100);
|
||||
|
||||
/*
|
||||
* Primary constructor, used to drive remainder of the test.
|
||||
*
|
||||
* Fork off the other side, then do your work.
|
||||
*/
|
||||
ResumptionUpdateBoundValues() throws Exception {
|
||||
final int count = 1;
|
||||
if (separateServerThread) {
|
||||
startServer(true);
|
||||
startClients(true, count);
|
||||
} else {
|
||||
startClients(true, count);
|
||||
startServer(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for other side to close down.
|
||||
*/
|
||||
Thread t;
|
||||
while ((t = threads.take()) != Thread.currentThread()) {
|
||||
System.out.printf(" joining: %s%n", t);
|
||||
t.join(1000L);
|
||||
}
|
||||
serverReady = false;
|
||||
System.gc();
|
||||
System.gc();
|
||||
|
||||
|
||||
SBListener listener = null;
|
||||
while ((listener = sbListeners.poll()) != null) {
|
||||
if (!listener.check()) {
|
||||
System.out.printf(" sbListener not called on finalize: %s%n",
|
||||
listener);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When we get here, the test is pretty much over.
|
||||
*
|
||||
* If the main thread excepted, that propagates back
|
||||
* immediately. If the other thread threw an exception, we
|
||||
* should report back.
|
||||
*/
|
||||
if (serverException != null) {
|
||||
System.out.print("Server Exception:");
|
||||
throw serverException;
|
||||
}
|
||||
if (clientException != null) {
|
||||
System.out.print("Client Exception:");
|
||||
throw clientException;
|
||||
}
|
||||
}
|
||||
|
||||
void startServer(boolean newThread) throws Exception {
|
||||
if (newThread) {
|
||||
Thread t = new Thread("Server") {
|
||||
public void run() {
|
||||
try {
|
||||
doServerSide();
|
||||
} catch (Exception e) {
|
||||
/*
|
||||
* Our server thread just died.
|
||||
*
|
||||
* Release the client, if not active already...
|
||||
*/
|
||||
System.err.println("Server died..." + e);
|
||||
serverReady = true;
|
||||
serverException = e;
|
||||
}
|
||||
}
|
||||
};
|
||||
threads.add(t);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
} else {
|
||||
doServerSide();
|
||||
}
|
||||
}
|
||||
|
||||
void startClients(boolean newThread, int count) throws Exception {
|
||||
for (int i = 0; i < count; i++) {
|
||||
System.out.printf(" newClient: %d%n", i);
|
||||
startClient(newThread);
|
||||
}
|
||||
serverReady = false;
|
||||
|
||||
threads.add(Thread.currentThread()); // add ourselves at the 'end'
|
||||
}
|
||||
void startClient(boolean newThread) throws Exception {
|
||||
if (newThread) {
|
||||
Thread t = new Thread("Client") {
|
||||
public void run() {
|
||||
try {
|
||||
sbListeners.add(doClientSide());
|
||||
} catch (Exception e) {
|
||||
/*
|
||||
* Our client thread just died.
|
||||
*/
|
||||
System.err.println("Client died..." + e);
|
||||
clientException = e;
|
||||
}
|
||||
}
|
||||
};
|
||||
System.out.printf(" starting: %s%n", t);
|
||||
threads.add(t);
|
||||
t.start();
|
||||
} else {
|
||||
sbListeners.add(doClientSide());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class SBListener implements SSLSessionBindingListener {
|
||||
private volatile int unboundNotified;
|
||||
private final WeakReference<SSLSession> session;
|
||||
|
||||
SBListener(SSLSession session) {
|
||||
this.unboundNotified = 0;
|
||||
this.session = new WeakReference<SSLSession>(session);
|
||||
}
|
||||
|
||||
boolean check() {
|
||||
System.out.printf(" check: %s%n", this);
|
||||
return unboundNotified > 0 && session.get() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueBound(SSLSessionBindingEvent event) {
|
||||
System.out.printf(" valueBound: %s%n", event.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueUnbound(SSLSessionBindingEvent event) {
|
||||
System.out.printf(" valueUnbound: %s%n", event.getName());
|
||||
unboundNotified++;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "count: " + unboundNotified +
|
||||
", ref: " + session.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,14 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 6948909
|
||||
* @bug 6948909 8217375
|
||||
* @summary Jarsigner removes MANIFEST.MF info for badly packages jar's
|
||||
* @library /test/lib
|
||||
*/
|
||||
/*
|
||||
* See also InsufficientSectionDelimiter.java for similar tests including cases
|
||||
* without or with different line breaks.
|
||||
*/
|
||||
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
@ -44,47 +48,47 @@ import java.util.zip.ZipOutputStream;
|
||||
public class DiffEnd {
|
||||
|
||||
static void check() throws Exception {
|
||||
SecurityTools.jarsigner("-keystore "
|
||||
+ Path.of(System.getProperty("test.src"), "JarSigning.keystore")
|
||||
.toString()
|
||||
+ " -storepass bbbbbb -digestalg SHA1"
|
||||
+ " -signedjar diffend.new.jar diffend.jar c");
|
||||
String ksArgs = "-keystore " + Path.of(System.getProperty("test.src"))
|
||||
.resolve("JarSigning.keystore") + " -storepass bbbbbb";
|
||||
|
||||
try (JarFile jf = new JarFile("diffend.new.jar")) {
|
||||
SecurityTools.jarsigner(ksArgs + " -digestalg SHA1 "
|
||||
+ "-signedjar diffend.signed.jar diffend.jar c")
|
||||
.shouldHaveExitValue(0);
|
||||
SecurityTools.jarsigner(" -verify " + ksArgs + " -verbose "
|
||||
+ "diffend.signed.jar c")
|
||||
.stdoutShouldMatch("^smk .* 1$").shouldHaveExitValue(0);
|
||||
|
||||
try (JarFile jf = new JarFile("diffend.signed.jar")) {
|
||||
Asserts.assertTrue(jf.getManifest().getMainAttributes()
|
||||
.containsKey(new Attributes.Name("Today")));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
// A MANIFEST.MF using \n as newlines and no double newlines at the end
|
||||
byte[] manifest =
|
||||
("Manifest-Version: 1.0\n"
|
||||
byte[] manifest = ("Manifest-Version: 1.0\n"
|
||||
+ "Created-By: 1.7.0-internal (Sun Microsystems Inc.)\n"
|
||||
+ "Today: Monday\n").getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// Without the fake .RSA file, to trigger the if (wasSigned) else block
|
||||
try (FileOutputStream fos = new FileOutputStream("diffend.jar");
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
zos.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
|
||||
zos.write(manifest);
|
||||
zos.putNextEntry(new ZipEntry("1"));
|
||||
zos.write(new byte[10]);
|
||||
}
|
||||
check();
|
||||
|
||||
// With the fake .RSA file, to trigger the if (wasSigned) block
|
||||
try (FileOutputStream fos = new FileOutputStream("diffend.jar");
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
zos.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
|
||||
zos.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
|
||||
zos.write(manifest);
|
||||
zos.putNextEntry(new ZipEntry("META-INF/x.RSA"));
|
||||
zos.putNextEntry(new ZipEntry("META-INF/x.RSA")); // fake .RSA
|
||||
zos.putNextEntry(new ZipEntry("1"));
|
||||
zos.write(new byte[10]);
|
||||
}
|
||||
|
||||
check();
|
||||
|
||||
// Without the fake .RSA file, to trigger the else block
|
||||
try (FileOutputStream fos = new FileOutputStream("diffend.jar");
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
zos.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
|
||||
zos.write(manifest);
|
||||
zos.putNextEntry(new ZipEntry("1"));
|
||||
zos.write(new byte[10]);
|
||||
}
|
||||
|
||||
check();
|
||||
}
|
||||
}
|
||||
|
166
test/jdk/sun/security/tools/jarsigner/DigestDontIgnoreCase.java
Normal file
166
test/jdk/sun/security/tools/jarsigner/DigestDontIgnoreCase.java
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @library /test/lib
|
||||
* @run testng DigestDontIgnoreCase
|
||||
* @summary Check that existing manifest digest entries are taken for valid
|
||||
* only if they match the actual digest value also taking upper and lower
|
||||
* case of the base64 encoded form of the digests into account.
|
||||
*/
|
||||
/*
|
||||
* <pre>mfDigest.equalsIgnoreCase(base64Digests[i])</pre>
|
||||
* previously in JarSigner.java on on line 985
|
||||
* @see jdk.security.jarsigner.JarSigner#updateDigests
|
||||
*/
|
||||
public class DigestDontIgnoreCase {
|
||||
|
||||
static final String KEYSTORE_FILENAME = "test.jks";
|
||||
|
||||
static final String DUMMY_FILE1 = "dummy1.txt";
|
||||
static final byte[] DUMMY_CONTENTS1 = DUMMY_FILE1.getBytes(UTF_8);
|
||||
static final String DUMMY_FILE2 = "dummy2.txt";
|
||||
static final byte[] DUMMY_CONTENTS2 = DUMMY_FILE2.getBytes(UTF_8);
|
||||
|
||||
byte[] goodSignedManifest;
|
||||
|
||||
@BeforeClass
|
||||
public void prepareCertificate() throws Exception {
|
||||
SecurityTools.keytool("-genkeypair -keyalg DSA -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
|
||||
+ " -alias a -dname CN=X").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
void prepareJarFile(String filename, Map<String, byte[]> contents)
|
||||
throws IOException {
|
||||
try (OutputStream out = Files.newOutputStream(Path.of(filename));
|
||||
JarOutputStream jos = new JarOutputStream(out)) {
|
||||
for (Map.Entry<String, byte[]> entry : contents.entrySet()) {
|
||||
JarEntry je = new JarEntry(entry.getKey());
|
||||
jos.putNextEntry(je);
|
||||
jos.write(entry.getValue());
|
||||
jos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeClass(dependsOnMethods = "prepareCertificate")
|
||||
public void prepareGoodSignedManifest() throws Exception {
|
||||
String filename = "prepare.jar";
|
||||
prepareJarFile(filename, Map.of(DUMMY_FILE1, DUMMY_CONTENTS1));
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + filename + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
goodSignedManifest = Utils.readJarManifestBytes(filename);
|
||||
Utils.echoManifest(goodSignedManifest,
|
||||
"reference manifest with one file signed");
|
||||
}
|
||||
|
||||
void testWithManifest(String filename, byte[] manifestBytes)
|
||||
throws Exception {
|
||||
Utils.echoManifest(manifestBytes,
|
||||
"going to test " + filename + " with manifest");
|
||||
prepareJarFile(filename, Map.of(
|
||||
JarFile.MANIFEST_NAME, manifestBytes,
|
||||
DUMMY_FILE1, DUMMY_CONTENTS1, // with digest already in manifest
|
||||
DUMMY_FILE2, DUMMY_CONTENTS2)); // causes manifest update
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(filename),
|
||||
filename + " created with manifest");
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -debug -verbose " + filename + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(filename),
|
||||
filename + " signed resulting in manifest");
|
||||
SecurityTools.jarsigner("-verify -strict -keystore " +
|
||||
KEYSTORE_FILENAME + " -storepass changeit -debug -verbose " +
|
||||
filename + " a").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyDigestGoodCase() throws Exception {
|
||||
testWithManifest("good.jar", goodSignedManifest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigestHeaderNameCase() throws Exception {
|
||||
byte[] mfBadHeader = new String(goodSignedManifest, UTF_8).
|
||||
replace("SHA-256-Digest", "sha-256-dIGEST").getBytes(UTF_8);
|
||||
testWithManifest("switch-header-name-case.jar", mfBadHeader);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigestWrongCase() throws Exception {
|
||||
byte[] mfBadDigest = switchCase(goodSignedManifest, "Digest");
|
||||
testWithManifest("switch-digest-case.jar", mfBadDigest);
|
||||
}
|
||||
|
||||
byte[] switchCase(byte[] manifest, String attrName) {
|
||||
byte[] wrongCase = Arrays.copyOf(manifest, manifest.length);
|
||||
byte[] name = (attrName + ":").getBytes(UTF_8);
|
||||
int matched = 0; // number of bytes before position i matching attrName
|
||||
for (int i = 0; i < wrongCase.length; i++) {
|
||||
if (wrongCase[i] == '\r' &&
|
||||
(i == wrongCase.length - 1 || wrongCase[i + 1] == '\n')) {
|
||||
continue;
|
||||
} else if ((wrongCase[i] == '\r' || wrongCase[i] == '\n')
|
||||
&& (i == wrongCase.length - 1 || wrongCase[i + 1] != ' ')) {
|
||||
matched = 0;
|
||||
} else if (matched == name.length) {
|
||||
wrongCase[i] = switchCase(wrongCase[i]);
|
||||
} else if (name[matched] == wrongCase[i]) {
|
||||
matched++;
|
||||
} else {
|
||||
matched = 0;
|
||||
}
|
||||
}
|
||||
return wrongCase;
|
||||
}
|
||||
|
||||
byte switchCase(byte c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return (byte) ('a' + (c - 'A'));
|
||||
} else if (c >= 'a' && c <= 'z') {
|
||||
return (byte) ('A' + (c - 'a'));
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Attributes.Name;
|
||||
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @library /test/lib
|
||||
* @modules java.base/java.util.jar:+open
|
||||
* @run testng/othervm EmptyIndividualSectionName
|
||||
* @summary Check that an individual section with an empty name is digested
|
||||
* and signed.
|
||||
* <p>
|
||||
* See also
|
||||
* jdk/test/jdk/sun/security/util/ManifestDigester/FindSections.java
|
||||
* for much more detailed api level tests
|
||||
*/
|
||||
public class EmptyIndividualSectionName {
|
||||
|
||||
static final String KEYSTORE_FILENAME = "test.jks";
|
||||
|
||||
@BeforeClass
|
||||
public void prepareCertificate() throws Exception {
|
||||
SecurityTools.keytool("-genkeypair -keyalg EC -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit "
|
||||
+ "-alias a -dname CN=X").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an additional section with name {@code sectionName} to the manifest
|
||||
* of a JAR before signing it with {@code signOpts}.
|
||||
* @return signature file {@code META-INF/A.SF} for further assertions
|
||||
*/
|
||||
Manifest test(String sectionName, String signOpts) throws Exception {
|
||||
Manifest mf = new Manifest();
|
||||
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||
mf.getEntries().put(sectionName, new Attributes());
|
||||
String jarFilename = "test" + sectionName +
|
||||
(signOpts != null ? signOpts : "") + ".jar";
|
||||
JarUtils.createJarFile(Path.of(jarFilename), mf, Path.of("."));
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " +
|
||||
(signOpts != null ? signOpts + " " : "") + jarFilename + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
SecurityTools.jarsigner("-verify -keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -debug -verbose " + jarFilename + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
|
||||
byte[] mfBytes = Utils.readJarManifestBytes(jarFilename);
|
||||
Utils.echoManifest(mfBytes, "manifest");
|
||||
mf = new Manifest(new ByteArrayInputStream(mfBytes));
|
||||
assertNotNull(mf.getAttributes(sectionName));
|
||||
byte[] sfBytes = Utils.readJarEntryBytes(jarFilename, "META-INF/A.SF");
|
||||
Utils.echoManifest(sfBytes, "signature file META-INF/A.SF");
|
||||
return new Manifest(new ByteArrayInputStream(sfBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that it makes a difference if the name is empty or not
|
||||
* by running the same test as {@link #testNameEmpty} with only a different
|
||||
* section name.
|
||||
*/
|
||||
@Test
|
||||
public void testNameNotEmpty() throws Exception {
|
||||
String sectionName = "X";
|
||||
assertNotNull(test(sectionName, null).getAttributes(sectionName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that individual sections are digested and signed also if the
|
||||
* name of such a section is empty.
|
||||
* An empty name of an individual section cannot be tested by adding a file
|
||||
* with an empty name to a JAR because such a file name is invalid and
|
||||
* cannot be used to add a file because it cannot be created or added to
|
||||
* the JAR file in the first place. However, an individual section with an
|
||||
* empty name can be added to the manifest.
|
||||
* Expected is a corresponding digest in the signature file which was not
|
||||
* present or produced before resolution of bug 8217375.
|
||||
*/
|
||||
@Test
|
||||
public void testNameEmpty() throws Exception {
|
||||
String sectionName = "";
|
||||
assertNotNull(test(sectionName, null).getAttributes(sectionName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #testNameEmpty} but tries to show a real difference
|
||||
* rather than just some internals in a {@code .SF} file, but TODO
|
||||
*/
|
||||
@Test(enabled = false, description = "TODO")
|
||||
public void testNameEmptyTrusted() throws Exception {
|
||||
String sectionName = "";
|
||||
test(sectionName, "-sectionsonly");
|
||||
String jarFilename = "test" + sectionName + "-sectionsonly.jar";
|
||||
try (JarFile jar = new JarFile(jarFilename, true)) {
|
||||
Manifest m = jar.getManifest();
|
||||
Method getTrustedAttributes = m.getClass()
|
||||
.getDeclaredMethod("getTrustedAttributes", String.class);
|
||||
getTrustedAttributes.setAccessible(true);
|
||||
assertThrows(SecurityException.class, () ->
|
||||
getTrustedAttributes.invoke(m, sectionName));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
72
test/jdk/sun/security/tools/jarsigner/EmptyJar.java
Normal file
72
test/jdk/sun/security/tools/jarsigner/EmptyJar.java
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.jar.Attributes.Name;
|
||||
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @modules java.base/sun.security.util
|
||||
* @library /test/lib /lib/testlibrary
|
||||
* @run testng EmptyJar
|
||||
* @summary Checks that signing an empty jar file does not result in an NPE or
|
||||
* other error condition.
|
||||
*/
|
||||
public class EmptyJar {
|
||||
|
||||
static final String KEYSTORE_FILENAME = "test.jks";
|
||||
|
||||
@BeforeClass
|
||||
public void prepareKeyStore() throws Exception {
|
||||
SecurityTools.keytool("-genkeypair -keyalg EC -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
|
||||
+ " -alias a -dname CN=A").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
String jarFilename = "test.jar";
|
||||
JarUtils.createJarFile(Path.of(jarFilename), (Manifest) null,
|
||||
Path.of("."));
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + jarFilename + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
|
||||
// verify that jarsigner has added a default manifest
|
||||
byte[] mfBytes = Utils.readJarManifestBytes(jarFilename);
|
||||
Utils.echoManifest(mfBytes, "manifest");
|
||||
assertTrue(new String(mfBytes, UTF_8).startsWith(
|
||||
Name.MANIFEST_VERSION + ": 1.0\r\nCreated-By: "));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import sun.security.util.ManifestDigester;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Factory;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @modules java.base/sun.security.util
|
||||
* @run testng FindHeaderEndVsManifestDigesterFindFirstSection
|
||||
* @summary Checks that {@link JarSigner#findHeaderEnd} (moved to now
|
||||
* {@link #findHeaderEnd} in this test) can be replaced with
|
||||
* {@link ManifestDigester#findSection}
|
||||
* (first invocation will identify main attributes)
|
||||
* without making a difference.
|
||||
*/
|
||||
/*
|
||||
* Note to future maintainer:
|
||||
* While it might look at first glance like this test ensures backwards-
|
||||
* compatibility between JarSigner.findHeaderEnd and
|
||||
* ManifestDigester.findSection's first invocation that find the main
|
||||
* attributes section, at the time of that change, this test continues to
|
||||
* verify main attributes digestion now with ManifestDigester.findSection as
|
||||
* opposed to previous implementation in JarSigner.findHeaderEnd.
|
||||
* Before completely removing this test, make sure that main attributes
|
||||
* digestion is covered appropriately with tests. After JarSigner.findHeaderEnd
|
||||
* has been removed digests should still continue to match.
|
||||
*
|
||||
* See also
|
||||
* - jdk/test/jdk/sun/security/tools/jarsigner/PreserveRawManifestEntryAndDigest.java
|
||||
* for some end-to-end tests utilizing the jarsigner tool,
|
||||
* - jdk/test/jdk/sun/security/util/ManifestDigester/FindSection.java and
|
||||
* - jdk/test/jdk/sun/security/util/ManifestDigester/DigestInput.java
|
||||
* for much more detailed tests at api level
|
||||
*
|
||||
* Both test mentioned above, however, originally were created when removing
|
||||
* confusion of "Manifest-Main-Attributes" individual section with actual main
|
||||
* attributes whereas the test here is about changes related to raw manifest
|
||||
* reproduction and in the end test pretty much the same behavior.
|
||||
*/
|
||||
public class FindHeaderEndVsManifestDigesterFindFirstSection {
|
||||
|
||||
static final boolean FIXED_8217375 = true; // FIXME
|
||||
|
||||
/**
|
||||
* from former {@link JarSigner#findHeaderEnd}, subject to verification if
|
||||
* it can be replaced with {@link ManifestDigester#findSection}
|
||||
*/
|
||||
@SuppressWarnings("fallthrough")
|
||||
private int findHeaderEnd(byte[] bs) {
|
||||
// Initial state true to deal with empty header
|
||||
boolean newline = true; // just met a newline
|
||||
int len = bs.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
switch (bs[i]) {
|
||||
case '\r':
|
||||
if (i < len - 1 && bs[i + 1] == '\n') i++;
|
||||
// fallthrough
|
||||
case '\n':
|
||||
if (newline) return i + 1; //+1 to get length
|
||||
newline = true;
|
||||
break;
|
||||
default:
|
||||
newline = false;
|
||||
}
|
||||
}
|
||||
// If header end is not found, it means the MANIFEST.MF has only
|
||||
// the main attributes section and it does not end with 2 newlines.
|
||||
// Returns the whole length so that it can be completely replaced.
|
||||
return len;
|
||||
}
|
||||
|
||||
@DataProvider(name = "parameters")
|
||||
public static Object[][] parameters() {
|
||||
List<Object[]> tests = new ArrayList<>();
|
||||
for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {
|
||||
if ("\r".equals(lineBreak) && !FIXED_8217375) continue;
|
||||
for (int numLBs = 0; numLBs <= 3; numLBs++) {
|
||||
for (String addSection : new String[] { null, "Ignore" }) {
|
||||
tests.add(new Object[] { lineBreak, numLBs, addSection });
|
||||
}
|
||||
}
|
||||
}
|
||||
return tests.toArray(new Object[tests.size()][]);
|
||||
}
|
||||
|
||||
@Factory(dataProvider = "parameters")
|
||||
public static Object[] createTests(String lineBreak, int numLineBreaks,
|
||||
String individualSectionName) {
|
||||
return new Object[]{new FindHeaderEndVsManifestDigesterFindFirstSection(
|
||||
lineBreak, numLineBreaks, individualSectionName
|
||||
)};
|
||||
}
|
||||
|
||||
final String lineBreak;
|
||||
final int numLineBreaks; // number of line breaks after main attributes
|
||||
final String individualSectionName; // null means only main attributes
|
||||
final byte[] rawBytes;
|
||||
|
||||
FindHeaderEndVsManifestDigesterFindFirstSection(String lineBreak,
|
||||
int numLineBreaks, String individualSectionName) {
|
||||
this.lineBreak = lineBreak;
|
||||
this.numLineBreaks = numLineBreaks;
|
||||
this.individualSectionName = individualSectionName;
|
||||
|
||||
rawBytes = (
|
||||
"oldStyle: trailing space " + lineBreak +
|
||||
"newStyle: no trailing space" + lineBreak.repeat(numLineBreaks) +
|
||||
// numLineBreaks < 2 will not properly delimit individual section
|
||||
// but it does not hurt to test that anyway
|
||||
(individualSectionName != null ?
|
||||
"Name: " + individualSectionName + lineBreak +
|
||||
"Ignore: nothing here" + lineBreak +
|
||||
lineBreak
|
||||
: "")
|
||||
).getBytes(UTF_8);
|
||||
}
|
||||
|
||||
@BeforeMethod
|
||||
public void verbose() {
|
||||
System.out.println("lineBreak = " + stringToIntList(lineBreak));
|
||||
System.out.println("numLineBreaks = " + numLineBreaks);
|
||||
System.out.println("individualSectionName = " + individualSectionName);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Callable {
|
||||
void call() throws Exception;
|
||||
}
|
||||
|
||||
void catchNoLineBreakAfterMainAttributes(Callable test) throws Exception {
|
||||
// manifests cannot be parsed and digested if the main attributes do
|
||||
// not end in a blank line (double line break) or one line break
|
||||
// immediately before eof.
|
||||
boolean failureExpected = numLineBreaks == 0
|
||||
&& individualSectionName == null;
|
||||
try {
|
||||
test.call();
|
||||
if (failureExpected) fail("expected an exception");
|
||||
} catch (NullPointerException | IllegalStateException e) {
|
||||
if (!failureExpected) fail("unexpected " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the beginning of the manifest until position<ol>
|
||||
* <li>{@code Jarsigner.findHeaderEnd} in the previous version
|
||||
* and</li>
|
||||
* <li>{@code ManifestDigester.getMainAttsEntry().sections[0].
|
||||
* lengthWithBlankLine} in the new version</li>
|
||||
* </ol>produce the same offset (TODO: or the same error).
|
||||
* The beginning of the manifest until that offset (range
|
||||
* <pre>0 .. (offset - 1)</pre>) will be reproduced if the manifest has
|
||||
* not changed.
|
||||
* <p>
|
||||
* Getting {@code startOfNext} of {@link ManifestDigester#findSection}'s
|
||||
* first invokation returned {@link ManifestDigester.Position} which
|
||||
* identifies the end offset of the main attributes is difficulted by
|
||||
* {@link ManifestDigester#findSection} being private and therefore not
|
||||
* directly accessible.
|
||||
*/
|
||||
@Test
|
||||
public void startOfNextLengthWithBlankLine() throws Exception {
|
||||
catchNoLineBreakAfterMainAttributes(() ->
|
||||
assertEquals(lengthWithBlankLine(), findHeaderEnd(rawBytes))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to its private visibility,
|
||||
* {@link ManifestDigester.Section#lengthWithBlankLine} is not directly
|
||||
* accessible. However, calling {@link ManifestDigester.Entry#digest}
|
||||
* reveals {@code lengthWithBlankLine} as third parameter in
|
||||
* <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>
|
||||
* on line ManifestDigester.java:212.
|
||||
* <p>
|
||||
* This value is the same as {@code startOfNext} of
|
||||
* {@link ManifestDigester#findSection}'s first invocation returned
|
||||
* {@link ManifestDigester.Position} identifying the end offset of the
|
||||
* main attributes because<ol>
|
||||
* <li>the end offset of the main attributes is assigned to
|
||||
* {@code startOfNext} in
|
||||
* <pre>pos.startOfNext = i+1;</pre> in ManifestDigester.java:98</li>
|
||||
* <li>which is then passed on as the third parameter to the constructor
|
||||
* of a new {@link ManifestDigester.Section#Section} by
|
||||
* <pre>new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)));</pre>
|
||||
* in in ManifestDigester.java:128</li>
|
||||
* <li>where it is assigned to
|
||||
* {@link ManifestDigester.Section#lengthWithBlankLine} by
|
||||
* <pre>this.lengthWithBlankLine = lengthWithBlankLine;</pre>
|
||||
* in ManifestDigester.java:241</li>
|
||||
* <li>from where it is picked up by {@link ManifestDigester.Entry#digest}
|
||||
* in
|
||||
* <pre>md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);</pre>
|
||||
* in ManifestDigester.java:212</li>
|
||||
* </ol>
|
||||
* all of which without any modification.
|
||||
*/
|
||||
int lengthWithBlankLine() {
|
||||
int[] lengthWithBlankLine = new int[] { 0 };
|
||||
new ManifestDigester(rawBytes).get(ManifestDigester.MF_MAIN_ATTRS,
|
||||
false).digest(new MessageDigest("lengthWithBlankLine") {
|
||||
@Override protected void engineReset() {
|
||||
lengthWithBlankLine[0] = 0;
|
||||
}
|
||||
@Override protected void engineUpdate(byte b) {
|
||||
lengthWithBlankLine[0]++;
|
||||
}
|
||||
@Override protected void engineUpdate(byte[] b, int o, int l) {
|
||||
lengthWithBlankLine[0] += l;
|
||||
}
|
||||
@Override protected byte[] engineDigest() {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return lengthWithBlankLine[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the replacement of {@link JarSigner#findHeaderEnd} is
|
||||
* actually used to reproduce manifest main attributes.
|
||||
* <p>
|
||||
* {@link #startOfNextLengthWithBlankLine} demonstrates that
|
||||
* {@link JarSigner#findHeaderEnd} has been replaced successfully with
|
||||
* {@link ManifestDigester#findSection} but does not also show that the
|
||||
* main attributes are reproduced with the same offset as before.
|
||||
* {@link #startOfNextLengthWithBlankLine} uses
|
||||
* {@link ManifestDigester.Entry#digest} to demonstrate an equal offset
|
||||
* calculated but {@link ManifestDigester.Entry#digest} is not necessarily
|
||||
* the same as reproducing, especially when considering
|
||||
* {@link ManifestDigester.Entry#oldStyle}.
|
||||
*/
|
||||
@Test(enabled = FIXED_8217375)
|
||||
public void reproduceMainAttributes() throws Exception {
|
||||
catchNoLineBreakAfterMainAttributes(() -> {
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
ManifestDigester md = new ManifestDigester(rawBytes);
|
||||
// without 8217375 fixed the following line will not even compile
|
||||
// so just remove it and skip the test for regression
|
||||
md.getMainAttsEntry().reproduceRaw(buf); // FIXME
|
||||
|
||||
assertEquals(buf.size(), findHeaderEnd(rawBytes));
|
||||
});
|
||||
}
|
||||
|
||||
static List<Integer> stringToIntList(String string) {
|
||||
byte[] bytes = string.getBytes(UTF_8);
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
list.add((int) bytes[i]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.jar.Attributes.Name;
|
||||
import java.util.jar.Manifest;
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Factory;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @library /test/lib
|
||||
* @run testng InsufficientSectionDelimiter
|
||||
* @summary Checks some cases signing a jar the manifest of which has no or
|
||||
* only one line break at the end and no proper delimiting blank line does not
|
||||
* result in an invalid signed jar without jarsigner noticing and failing.
|
||||
*
|
||||
* <p>See also<ul>
|
||||
* <li>{@link PreserveRawManifestEntryAndDigest} with an update of a signed
|
||||
* jar with a different signer whereas this test just signs with one signer
|
||||
* </li>
|
||||
* <li>{@link WasSignedByOtherSigner} for a test that detects if
|
||||
* {@code wasSigned} in {@link jdk.security.jarsigner.JarSigner#sign0} was set
|
||||
* correctly determining whether or not to re-write the manifest, and</li>
|
||||
* <li>{@code diffend.sh} for another similar test</li></ul>
|
||||
*/
|
||||
public class InsufficientSectionDelimiter {
|
||||
|
||||
static final String KEYSTORE_FILENAME = "test.jks";
|
||||
|
||||
@BeforeTest
|
||||
public void prepareCertificate() throws Exception {
|
||||
SecurityTools.keytool("-genkeypair -keyalg EC -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
|
||||
+ " -alias a -dname CN=A").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
@BeforeTest
|
||||
public void prepareFakeSfFile() throws IOException {
|
||||
new File("META-INF").mkdir();
|
||||
Files.write(Path.of("META-INF/.SF"), (
|
||||
Name.SIGNATURE_VERSION + ": 1.0\r\n" +
|
||||
"-Digest-Manifest: \r\n\r\n").getBytes(UTF_8));
|
||||
}
|
||||
|
||||
@DataProvider(name = "parameters")
|
||||
public static Object[][] parameters() {
|
||||
return new String[][] { { "" }, { "\n" }, { "\r" }, { "\r\n" } };
|
||||
}
|
||||
|
||||
@Factory(dataProvider = "parameters")
|
||||
public static Object[] createTests(String lineBreak) {
|
||||
return new Object[] { new InsufficientSectionDelimiter(lineBreak) };
|
||||
}
|
||||
|
||||
final String lineBreak;
|
||||
final String jarFilenameSuffix;
|
||||
|
||||
InsufficientSectionDelimiter(String lineBreak) {
|
||||
this.lineBreak = lineBreak;
|
||||
jarFilenameSuffix = Utils.escapeStringWithNumbers(lineBreak);
|
||||
}
|
||||
|
||||
@BeforeMethod
|
||||
public void verbose() {
|
||||
System.out.println("lineBreak = "
|
||||
+ Utils.escapeStringWithNumbers(lineBreak));
|
||||
}
|
||||
|
||||
void test(String jarFilenamePrefix, String... files) throws Exception {
|
||||
String jarFilename = jarFilenamePrefix + jarFilenameSuffix + ".jar";
|
||||
JarUtils.createJarFile(Path.of(jarFilename), new Manifest() {
|
||||
@Override public void write(OutputStream out) throws IOException {
|
||||
out.write((Name.MANIFEST_VERSION + ": 1.0" +
|
||||
lineBreak).getBytes(UTF_8));
|
||||
}
|
||||
}, Path.of("."), Stream.of(files).map(Path::of).toArray(Path[]::new)
|
||||
);
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename), "unsigned jar");
|
||||
try {
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + jarFilename +
|
||||
" a").shouldHaveExitValue(0);
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename), "signed jar");
|
||||
} catch (Exception e) {
|
||||
if (lineBreak.isEmpty()) {
|
||||
return; // invalid manifest without trailing line break
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
// remove META-INF/.SF from signed jar which would not validate
|
||||
// (not added in all the test cases)
|
||||
JarUtils.updateJar(jarFilename, "verify-" + jarFilename,
|
||||
Map.of("META-INF/.SF", false));
|
||||
SecurityTools.jarsigner("-verify -strict -keystore " +
|
||||
KEYSTORE_FILENAME + " -storepass changeit -debug -verbose " +
|
||||
"verify-" + jarFilename + " a").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that signing a jar which has never been signed yet and contains
|
||||
* no signature related files with a manifest that ends immediately after
|
||||
* the last main attributes value byte or only one line break and no blank
|
||||
* line produces a valid signed jar or an error if the manifest ends
|
||||
* without line break.
|
||||
*/
|
||||
@Test
|
||||
public void testOnlyMainAttrs() throws Exception {
|
||||
test("testOnlyMainAttrs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that signing a jar with a manifest that ends immediately after
|
||||
* the last main attributes value byte or with too few line break
|
||||
* characters to properly delimit an individual section and has a fake
|
||||
* signing related file to trigger a signature update or more specifically
|
||||
* wasSigned in JarSigner.sign0 to become true produces a valid signed jar
|
||||
* or an error if the manifest ends without line break.
|
||||
* <p>
|
||||
* Only one line break and hence no blank line ('\r', '\n', or '\r\n')
|
||||
* after last main attributes value byte is too little to delimit an
|
||||
* individual section to hold a file's digest but acceptable if no
|
||||
* individual section has to be added because no contained file has to be
|
||||
* signed as is the case in this test.
|
||||
*
|
||||
* @see #testMainAttrsWasSignedAddFile
|
||||
*/
|
||||
@Test
|
||||
public void testMainAttrsWasSigned() throws Exception {
|
||||
test("testMainAttrsWasSigned", "META-INF/.SF");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that signing a jar with a manifest that ends immediately after
|
||||
* the last main attributes value byte or with too few line break
|
||||
* characters to properly delimit an individual section and has a fake
|
||||
* signing related file to trigger a signature update or more specifically
|
||||
* wasSigned in JarSigner.sign0 to become true produces no invalid signed
|
||||
* jar or an error if the manifest ends without line break.
|
||||
* <p>
|
||||
* Only one line break and hence no blank line ('\r', '\n', or '\r\n')
|
||||
* after the last main attributes value byte is too little to delimit an
|
||||
* individual section which would be required here to save the digest of a
|
||||
* contained file to be signed.
|
||||
* <p>
|
||||
* Changing the delimiters after the main attributes changes the main
|
||||
* attributes digest but
|
||||
* {@link SignatureFileVerifier#verifyManifestMainAttrs} and
|
||||
* {@link ManifestDigester#digestWorkaround} work around it.
|
||||
*/
|
||||
@Test
|
||||
public void testMainAttrsWasSignedAddFile() throws Exception {
|
||||
Files.write(Path.of("test.txt"), "test.txt".getBytes(UTF_8));
|
||||
test("testMainAttrsWasSignedAddFile", "META-INF/.SF", "test.txt");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Attributes.Name;
|
||||
import sun.security.util.ManifestDigester;
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @modules java.base/sun.security.util
|
||||
* @library /test/lib /lib/testlibrary
|
||||
* @run testng MainAttributesConfused
|
||||
* @summary Check that manifest individual section "Manifest-Main-Attributes"
|
||||
* does not interfere and is not confused with ManifestDigester internals.
|
||||
*
|
||||
* See also
|
||||
* jdk/test/jdk/sun/security/util/ManifestDigester/ManifestMainAttributes.java
|
||||
* for much more detailed api level tests
|
||||
*/
|
||||
public class MainAttributesConfused {
|
||||
|
||||
static final String KEYSTORE_FILENAME = "test.jks";
|
||||
static final String MAIN_ATTRIBUTES_MARKER = null;
|
||||
|
||||
@BeforeClass
|
||||
void prepareKeyStore() throws Exception {
|
||||
SecurityTools.keytool("-genkeypair -keyalg EC -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
|
||||
+ " -alias a -dname CN=X").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
void testAddManifestSection(String sectionName) throws Exception {
|
||||
// create a signed jar
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||
String testFile = "test-" + sectionName;
|
||||
Files.write(Path.of(testFile), testFile.getBytes(UTF_8));
|
||||
String jarFilename = sectionName + ".jar";
|
||||
JarUtils.createJarFile(Path.of(jarFilename), manifest,
|
||||
Path.of("."), Path.of(testFile));
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + jarFilename + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
|
||||
// get the manifest of the signed jar with the signature digests, add
|
||||
// a new individual section, and write it back
|
||||
try (JarFile jar = new JarFile(jarFilename)) {
|
||||
manifest = jar.getManifest();
|
||||
}
|
||||
Attributes attrs = sectionName == MAIN_ATTRIBUTES_MARKER
|
||||
? manifest.getMainAttributes()
|
||||
: manifest.getEntries().computeIfAbsent(sectionName,
|
||||
n -> new Attributes());
|
||||
attrs.put(new Name("Some-Key"), "Some-Value");
|
||||
String jarFilenameAttrs = sectionName + "-attrs.jar";
|
||||
JarUtils.updateManifest(jarFilename, jarFilenameAttrs, manifest);
|
||||
|
||||
// having just added another manifest entry (individual section) not
|
||||
// modifying existing digests or main attributes should not invalidate
|
||||
// the existing signature.
|
||||
SecurityTools.jarsigner("-verify -keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -debug -verbose " + jarFilenameAttrs +
|
||||
" a").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddOtherThanManifestMainAttributes() throws Exception {
|
||||
// any value but "Manifest-Main-Attributes", even lower case works
|
||||
testAddManifestSection("manifest-main-attributes");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddMainAttributesHeader() throws Exception {
|
||||
// adding or changing existing attributes of the main section, however,
|
||||
// will invalidate the signature
|
||||
assertThrows(() -> testAddManifestSection(MAIN_ATTRIBUTES_MARKER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddManifestMainAttributesSection() throws Exception {
|
||||
testAddManifestSection(ManifestDigester.MF_MAIN_ATTRS);
|
||||
}
|
||||
|
||||
}
|
@ -23,10 +23,15 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 6543940 6868865
|
||||
* @bug 6543940 6868865 8217375
|
||||
* @summary Exception thrown when signing a jarfile in java 1.5
|
||||
* @library /test/lib
|
||||
*/
|
||||
/*
|
||||
* See also PreserveRawManifestEntryAndDigest.java for tests with arbitrarily
|
||||
* formatted individual sections in addition the the main attributes tested
|
||||
* here.
|
||||
*/
|
||||
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
@ -44,8 +49,11 @@ public class OldSig {
|
||||
JarUtils.updateJarFile(Path.of("B.jar"), Path.of("."),
|
||||
Path.of("B.class"));
|
||||
|
||||
SecurityTools.jarsigner("-keystore " + src.resolve("JarSigning.keystore")
|
||||
+ " -storepass bbbbbb -digestalg SHA1 B.jar c");
|
||||
SecurityTools.jarsigner("-verify B.jar");
|
||||
String ksArgs = "-keystore " + src.resolve("JarSigning.keystore")
|
||||
+ " -storepass bbbbbb";
|
||||
SecurityTools.jarsigner(ksArgs + " -digestalg SHA1 B.jar c");
|
||||
SecurityTools.jarsigner("-verify B.jar").shouldHaveExitValue(0);
|
||||
SecurityTools.jarsigner("-verify " + ksArgs + " -verbose B.jar c")
|
||||
.stdoutShouldMatch("^smk .* B[.]class$").shouldHaveExitValue(0);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.jar.Attributes.Name;
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @library /test/lib
|
||||
* @run testng RemoveDifferentKeyAlgBlockFile
|
||||
* @summary Checks that if a signed jar file is signed again with the same
|
||||
* signer name and a different algorithm that the signature block file for
|
||||
* the previous signature is removed. Example: the jar had META-INF/A.SF and
|
||||
* META-INF/A.RSA files and is now signed with DSA. So it should contain
|
||||
* an updated META-INF/A.SF and META-INF/A.DSA and the META-INF/A.RSA should
|
||||
* be removed because not valid any longer.
|
||||
*/
|
||||
public class RemoveDifferentKeyAlgBlockFile {
|
||||
|
||||
static final String KEYSTORE_FILENAME = "test.jks";
|
||||
|
||||
@BeforeClass
|
||||
public void prepareCertificates() throws Exception {
|
||||
SecurityTools.keytool("-genkeypair -keyalg RSA -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
|
||||
+ " -alias RSA -dname CN=RSA").shouldHaveExitValue(0);
|
||||
SecurityTools.keytool("-genkeypair -keyalg DSA -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
|
||||
+ " -alias DSA -dname CN=DSA").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherAlgSigBlockFileRemoved() throws Exception {
|
||||
String jarFilename = "test.jar";
|
||||
JarUtils.createJarFile(Path.of(jarFilename), (Manifest) null,
|
||||
Path.of("."));
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug -sigfile A " +
|
||||
jarFilename + " RSA").shouldHaveExitValue(0);
|
||||
|
||||
// change the jar file to invalidate the first signature with RSA
|
||||
String jarFilenameModified = "modified.jar";
|
||||
try (JarFile jar = new JarFile(jarFilename)) {
|
||||
Manifest manifest = jar.getManifest();
|
||||
manifest.getMainAttributes().put(
|
||||
new Name("Some-Key"), "Some-Value");
|
||||
JarUtils.updateManifest(jarFilename, jarFilenameModified, manifest);
|
||||
}
|
||||
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug -sigfile A " +
|
||||
jarFilenameModified + " DSA").shouldHaveExitValue(0);
|
||||
SecurityTools.jarsigner("-verify -keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -debug -verbose " + jarFilenameModified)
|
||||
.shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.jar.JarFile;
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @library /test/lib
|
||||
* @run testng SectionNameContinuedVsLineBreak
|
||||
* @summary Checks some specific line break character sequences in section name
|
||||
* continuation line breaks.
|
||||
*/
|
||||
public class SectionNameContinuedVsLineBreak {
|
||||
|
||||
static final String KEYSTORE_FILENAME = "test.jks";
|
||||
|
||||
@BeforeTest
|
||||
public void prepareCertificate() throws Exception {
|
||||
SecurityTools.keytool("-genkeypair -keyalg EC -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
|
||||
+ " -alias a -dname CN=A").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
void manipulateManifestSignAgainA(
|
||||
String srcJarFilename, String dstJarFilename,
|
||||
Function<Manifest, byte[]> manifestManipulation) throws Exception {
|
||||
byte[] manipulatedManifest = manifestManipulation.apply(
|
||||
new Manifest(new ByteArrayInputStream(
|
||||
Utils.readJarManifestBytes(srcJarFilename))));
|
||||
Utils.echoManifest(manipulatedManifest, "manipulated manifest");
|
||||
JarUtils.updateJar(srcJarFilename, dstJarFilename, Map.of(
|
||||
JarFile.MANIFEST_NAME, manipulatedManifest));
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + dstJarFilename + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
dstJarFilename), "manipulated jar signed again with a");
|
||||
// check assumption that jar is valid at this point
|
||||
SecurityTools.jarsigner("-verify -keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + dstJarFilename + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
void test(String name, Function<Manifest, byte[]> manifestManipulation,
|
||||
String jarContentFilename) throws Exception {
|
||||
String jarFilename1 = "test-" + name + "-step1.jar";
|
||||
Files.write(Path.of(jarContentFilename),
|
||||
jarContentFilename.getBytes(UTF_8));
|
||||
JarUtils.createJarFile(Path.of(jarFilename1), (Manifest)
|
||||
/* no manifest will let jarsigner create a default one */ null,
|
||||
Path.of("."), Path.of(jarContentFilename));
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + jarFilename1 +
|
||||
" a").shouldHaveExitValue(0);
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename1), "signed jar");
|
||||
String jarFilename2 = "test-" + name + "-step2.jar";
|
||||
manipulateManifestSignAgainA(jarFilename1, jarFilename2,
|
||||
manifestManipulation);
|
||||
|
||||
SecurityTools.jarsigner("-verify -strict -keystore " +
|
||||
KEYSTORE_FILENAME + " -storepass changeit -debug -verbose " +
|
||||
jarFilename2 + " a").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test signing a jar with a manifest that has an entry the name of
|
||||
* which continued on a continuation line with '\r' as line break before
|
||||
* the continuation line space ' ' on the line the name starts.
|
||||
*/
|
||||
@Test
|
||||
public void testContinueNameAfterCr() throws Exception {
|
||||
String filename = "abc";
|
||||
test("testContinueNameAfterCr", m -> {
|
||||
String digest = m.getAttributes("abc").getValue("SHA-256-Digest");
|
||||
m.getEntries().remove("abc");
|
||||
return (manifestToString(m)
|
||||
+ "Name: a\r"
|
||||
+ " bc\r\n"
|
||||
+ "SHA-256-Digest: " + digest + "\r\n"
|
||||
+ "\r\n").getBytes(UTF_8);
|
||||
}, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test signing a jar with a manifest that has an entry the name of
|
||||
* which continued on a continuation line with '\r' as line break before
|
||||
* the continuation line space ' ' after a first continuation.
|
||||
*/
|
||||
@Test
|
||||
public void testContinueNameAfterCrOnContinuationLine() throws Exception {
|
||||
String filename = "abc";
|
||||
test("testContinueNameAfterCr", m -> {
|
||||
String digest = m.getAttributes("abc").getValue("SHA-256-Digest");
|
||||
m.getEntries().remove("abc");
|
||||
return (manifestToString(m)
|
||||
+ "Name: a\r\n"
|
||||
+ " b\r"
|
||||
+ " c\r\n"
|
||||
+ "SHA-256-Digest: " + digest + "\r\n"
|
||||
+ "\r\n").getBytes(UTF_8);
|
||||
}, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test signing a jar with a manifest that has an entry the name of
|
||||
* which continued on a continuation line and terminated with '\r' as line
|
||||
* break after the name.
|
||||
*/
|
||||
@Test
|
||||
public void testEndNameWithCrOnContinuationLine() throws Exception {
|
||||
String filename = "abc";
|
||||
test("testContinueNameAfterCr", m -> {
|
||||
String digest = m.getAttributes("abc").getValue("SHA-256-Digest");
|
||||
m.getEntries().remove("abc");
|
||||
return (manifestToString(m)
|
||||
+ "Name: a\r\n"
|
||||
+ " bc\r"
|
||||
+ "SHA-256-Digest: " + digest + "\r\n"
|
||||
+ "\r\n").getBytes(UTF_8);
|
||||
}, filename);
|
||||
}
|
||||
|
||||
String manifestToString(Manifest mf) {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
mf.write(out);
|
||||
return out.toString(UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2019, 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
|
||||
@ -21,8 +21,15 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Helper class.
|
||||
@ -35,5 +42,66 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
static void printNonPrintableCharactersEscaped(byte[] manifest)
|
||||
throws IOException {
|
||||
// keep byte sequences encoding multi-byte UTF-8 encoded and composite
|
||||
// characters together as much as possible before decoding into a String
|
||||
ByteArrayOutputStream lineBuf = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < manifest.length; i++) {
|
||||
switch (manifest[i]) {
|
||||
case '\t':
|
||||
lineBuf.write("\\t".getBytes(UTF_8));
|
||||
break;
|
||||
case '\r':
|
||||
lineBuf.write("\\r".getBytes(UTF_8));
|
||||
if (i + 1 >= manifest.length || manifest[i + 1] != '\n') {
|
||||
System.out.println(lineBuf.toString(UTF_8));
|
||||
lineBuf.reset();
|
||||
}
|
||||
break;
|
||||
case '\n':
|
||||
lineBuf.write("\\n".getBytes(UTF_8));
|
||||
System.out.println(lineBuf.toString(UTF_8));
|
||||
lineBuf.reset();
|
||||
break;
|
||||
default:
|
||||
lineBuf.write(manifest[i]);
|
||||
}
|
||||
}
|
||||
if (lineBuf.size() > 0) {
|
||||
System.out.println(lineBuf.toString(UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
static void echoManifest(byte[] manifest, String msg) throws IOException {
|
||||
System.out.println("-".repeat(72));
|
||||
System.out.println(msg);
|
||||
System.out.println("-".repeat(72));
|
||||
printNonPrintableCharactersEscaped(manifest);
|
||||
System.out.println("-".repeat(72));
|
||||
}
|
||||
|
||||
static byte[] readJarManifestBytes(String jarFilename) throws IOException {
|
||||
return readJarEntryBytes(jarFilename, JarFile.MANIFEST_NAME);
|
||||
}
|
||||
|
||||
static byte[] readJarEntryBytes(String jarFilename, String jarEntryname)
|
||||
throws IOException {
|
||||
try (
|
||||
ZipFile jar = new ZipFile(jarFilename);
|
||||
InputStream is = jar.getInputStream(jar.getEntry(jarEntryname));
|
||||
) {
|
||||
return is.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
static String escapeStringWithNumbers(String lineBreak) {
|
||||
String escaped = "";
|
||||
byte[] bytes = lineBreak.getBytes(UTF_8);
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
escaped += bytes[i];
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.jar.Attributes.Name;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @library /test/lib
|
||||
* @run testng WasSignedByOtherSigner
|
||||
* @summary Checks that {@code wasSigned} in
|
||||
* {@link jdk.security.jarsigner.JarSigner#sign0} is set true if the jar to sign
|
||||
* contains a signature that will not be overwritten with the current one.
|
||||
*/
|
||||
public class WasSignedByOtherSigner {
|
||||
|
||||
static final String KEYSTORE_FILENAME = "test.jks";
|
||||
|
||||
@BeforeClass
|
||||
public void prepareKeyStore() throws Exception {
|
||||
SecurityTools.keytool("-genkeypair -keyalg EC -keystore "
|
||||
+ KEYSTORE_FILENAME + " -storepass changeit -keypass changeit"
|
||||
+ " -alias a -dname CN=A").shouldHaveExitValue(0);
|
||||
}
|
||||
|
||||
void test(String secondSigner, boolean expRrewritten) throws Exception {
|
||||
String jarFilename1 = "test" + secondSigner + "-1.jar";
|
||||
JarUtils.createJarFile(Path.of(jarFilename1), (Manifest) null,
|
||||
Path.of("."));
|
||||
// TODO: use jarsigner here only to create a default manifest...
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + jarFilename1 + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename1), "initial manifest");
|
||||
|
||||
// replace manifest with a non-standard one that can later be checked
|
||||
String jarFilename2 = "test" + secondSigner + "-2.jar";
|
||||
JarUtils.updateJar(jarFilename1, jarFilename2, Map.of(
|
||||
// add a fake sig-related file to trigger wasSigned in JarSigner
|
||||
"META-INF/.SF", Name.SIGNATURE_VERSION + ": 1.0\r\n"));
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename2), "with fake META-INF.SF file");
|
||||
String jarFilename3 = "test" + secondSigner + "-3.jar";
|
||||
JarUtils.updateManifest(jarFilename2, jarFilename3, new Manifest() {
|
||||
@Override public void write(OutputStream out) throws IOException {
|
||||
// no trailing blank line
|
||||
out.write((Name.MANIFEST_VERSION + ": 1.0\r\n").getBytes(UTF_8));
|
||||
}
|
||||
});
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename3), "with manifest manipulated");
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug " + jarFilename3 + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename3), "signed");
|
||||
String jarFilename4 = "test" + secondSigner + "-4.jar";
|
||||
JarUtils.updateJar(jarFilename3, jarFilename4,
|
||||
Map.of("META-INF/.SF", false));
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename4), "with fake META-INF.SF file removed");
|
||||
|
||||
// re-sign the jar with signer named secondSigner (same or different)
|
||||
SecurityTools.jarsigner("-keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -verbose -debug -sigfile " +
|
||||
secondSigner + " " + jarFilename4 + " a")
|
||||
.shouldHaveExitValue(0);
|
||||
Utils.echoManifest(Utils.readJarManifestBytes(
|
||||
jarFilename4), "signed again");
|
||||
// remove META-INF/.SF from signed jar again which would not validate
|
||||
|
||||
// in any case verify that the resulting jar file is valid
|
||||
SecurityTools.jarsigner("-verify -keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -debug -verbose " + jarFilename4)
|
||||
.shouldHaveExitValue(0);
|
||||
SecurityTools.jarsigner("-verify -keystore " + KEYSTORE_FILENAME +
|
||||
" -storepass changeit -debug -verbose " + jarFilename4 +
|
||||
" a").shouldHaveExitValue(0);
|
||||
|
||||
// if wasSigned was true in JarSigner#sign0 the manifest (only main
|
||||
// attributes present and tested here but same consideration applies
|
||||
// to individual sections just the same) should be reproduced with
|
||||
// unchanged binary form. Otherwise, if there were no previous
|
||||
// signatures or only one being replaced, the manifest is kind of
|
||||
// "normalized" by re-writing it thereby replacing all line breaks
|
||||
// (from cr or lf to crlf) and replacing all line breaks onto
|
||||
// continuation lines and also writing all section delimiting blank
|
||||
// lines.
|
||||
// if that "normalization" has took place the test here can conclude
|
||||
// whether wasSigned was true or was not.
|
||||
try (ZipFile jar = new ZipFile(jarFilename4)) {
|
||||
ZipEntry ze = jar.getEntry(JarFile.MANIFEST_NAME);
|
||||
byte[] manifestBytes = jar.getInputStream(ze).readAllBytes();
|
||||
Utils.echoManifest(manifestBytes, "manifest");
|
||||
String manifestString = new String(manifestBytes, UTF_8);
|
||||
boolean actRewritten = manifestString.endsWith("\r\n\r\n");
|
||||
assertEquals(actRewritten, expRrewritten);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reSignSameSigner() throws Exception {
|
||||
test("A", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reSignOtherSigner() throws Exception {
|
||||
test("B", false);
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2019, 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
|
||||
@ -34,8 +34,9 @@ public class DetailsOutputStream extends FileOutputStream {
|
||||
|
||||
private PhaseOutputStream phaseOutputStream = new PhaseOutputStream();
|
||||
|
||||
public DetailsOutputStream() throws FileNotFoundException {
|
||||
super("details.out", true);
|
||||
public DetailsOutputStream(String filename) throws FileNotFoundException {
|
||||
super(filename != null && !filename.isEmpty() ? filename :
|
||||
"details.out", true);
|
||||
}
|
||||
|
||||
public void transferPhase() throws IOException {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2019, 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
|
||||
@ -60,7 +60,7 @@ public class HtmlHelper {
|
||||
}
|
||||
|
||||
public static String startTable() {
|
||||
return startTag("table " + STYLE);
|
||||
return startTag("table border=\"1\" padding=\"1\" cellspacing=\"0\" " + STYLE);
|
||||
}
|
||||
|
||||
public static String endTable() {
|
||||
@ -68,19 +68,19 @@ public class HtmlHelper {
|
||||
}
|
||||
|
||||
public static String startTr() {
|
||||
return startTag("tr");
|
||||
return "\t" + startTag("tr") + "\n";
|
||||
}
|
||||
|
||||
public static String endTr() {
|
||||
return endTag("tr");
|
||||
return "\t" + endTag("tr") + "\n";
|
||||
}
|
||||
|
||||
public static String startTd() {
|
||||
return startTag("td");
|
||||
return "\t\t" + startTag("td");
|
||||
}
|
||||
|
||||
public static String endTd() {
|
||||
return endTag("td");
|
||||
return endTag("td") + "\n";
|
||||
}
|
||||
|
||||
public static String startTag(String tag) {
|
||||
@ -92,7 +92,7 @@ public class HtmlHelper {
|
||||
}
|
||||
|
||||
public static String anchorName(String name, String text) {
|
||||
return "<a name=" + name + ">" + text + "</a>";
|
||||
return "<a name=" + name + "><hr/>" + text + "</a>";
|
||||
}
|
||||
|
||||
public static String anchorLink(String file, String anchorName,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2019, 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
|
||||
@ -21,41 +21,59 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/*
|
||||
* This class is used for returning some specific JDK information.
|
||||
*/
|
||||
public class JdkUtils {
|
||||
|
||||
private enum Alg {
|
||||
KEY, SIG, DIGEST;
|
||||
}
|
||||
|
||||
static final String M_JAVA_RUNTIME_VERSION = "javaRuntimeVersion";
|
||||
static final String M_IS_SUPPORTED_KEYALG = "isSupportedKeyalg";
|
||||
static final String M_IS_SUPPORTED_SIGALG = "isSupportedSigalg";
|
||||
static final String M_IS_SUPPORTED_DIGESTALG = "isSupportedDigestalg";
|
||||
|
||||
// Returns the JDK build version.
|
||||
static String javaRuntimeVersion() {
|
||||
return System.getProperty("java.runtime.version");
|
||||
}
|
||||
|
||||
// Checks if the specified signature algorithm is supported by the JDK.
|
||||
static boolean isSupportedSigalg(String sigalg) {
|
||||
boolean isSupported = false;
|
||||
// Checks if the specified algorithm is supported by the JDK.
|
||||
static boolean isSupportedAlg(Alg algType, String algName) {
|
||||
try {
|
||||
isSupported = Signature.getInstance(sigalg) != null;
|
||||
switch (algType) {
|
||||
case KEY:
|
||||
return KeyPairGenerator.getInstance(algName) != null;
|
||||
case SIG:
|
||||
return Signature.getInstance(algName) != null;
|
||||
case DIGEST:
|
||||
return MessageDigest.getInstance(algName) != null;
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) { }
|
||||
|
||||
if (!isSupported) {
|
||||
System.out.println(sigalg + " is not supported yet.");
|
||||
}
|
||||
|
||||
return isSupported;
|
||||
System.out.println(algName + " is not supported yet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (M_JAVA_RUNTIME_VERSION.equals(args[0])) {
|
||||
System.out.print(javaRuntimeVersion());
|
||||
} else if (M_IS_SUPPORTED_KEYALG.equals(args[0])) {
|
||||
System.out.print(isSupportedAlg(Alg.KEY, args[1]));
|
||||
} else if (M_IS_SUPPORTED_SIGALG.equals(args[0])) {
|
||||
System.out.print(isSupportedSigalg(args[1]));
|
||||
System.out.print(isSupportedAlg(Alg.SIG, args[1]));
|
||||
} else if (M_IS_SUPPORTED_DIGESTALG.equals(args[0])) {
|
||||
System.out.print(isSupportedAlg(Alg.DIGEST, args[1]));
|
||||
} else {
|
||||
throw new IllegalArgumentException("invalid: " + asList(args));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2017, 2019, 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
|
||||
@ -35,6 +35,10 @@ TSA list are big, and that would lead to jtreg output overflow. So, it redirects
|
||||
stdout and stderr to file JTwork/scratch/details.out.
|
||||
|
||||
##### Report Columns #####
|
||||
|
||||
Jarfile
|
||||
The filenames used in the tests
|
||||
|
||||
Certificate
|
||||
Certificate identifier. The identifier consists of specific attributes of
|
||||
the certificate. Generally, the naming convention is:
|
||||
@ -44,10 +48,23 @@ Signer JDK
|
||||
The JDK version that signs jar.
|
||||
|
||||
Signature Algorithm
|
||||
The signature algorithm used by signing.
|
||||
The signature algorithm used to sign the key as in 'keytool -sigalg'.
|
||||
|
||||
TSA Digest
|
||||
The timestamp digest algorithm used by signing.
|
||||
Note: The values displayed in this column are specified to jarsigner only
|
||||
in case a test does not work with a default value.
|
||||
In any case the specified value or expected default value is compared in
|
||||
verifying phase against jarsigner's output and the test fails if it does
|
||||
not match.
|
||||
|
||||
Jar Digest Algorithm
|
||||
The digest algorithm used to digest the files contained in the JAR file and
|
||||
the manifest and signature files as in 'jarsigner -digestalg'.
|
||||
See also note above about default values for Signature Algorithm.
|
||||
|
||||
TSA Digest Algorithm
|
||||
The timestamp digest algorithm used by TSA as in 'jarsigner -tsadigestalg'.
|
||||
Shows no value if no TSA used.
|
||||
See also note above about default values for Signature Algorithm.
|
||||
|
||||
TSA
|
||||
TSA URL index. All of TSA URLs and their indices can be found at the top
|
||||
@ -80,24 +97,38 @@ jtreg [-options] \
|
||||
-jdk:<path/to/testing/JDK>
|
||||
[-DproxyHost=<host> \
|
||||
-DproxyPort=<port> \
|
||||
-Dteeout=filename \
|
||||
-DtsaListFile=</url/to/tsaListFile> \
|
||||
-DtsaList=</path/to/tsa1#/path/to/tsa2#/path/to/tsa3#...> \
|
||||
-DtsaList=</path/to/tsa1#/path/to/tsa2#/path/to/tsa3#...|notsa> \
|
||||
-DjdkListFile=</path/to/jdkListFile> \
|
||||
-DjdkList=</path/to/jdk1#/path/to/jdk2#/path/to/jdk3#...> \
|
||||
-DjavaSecurityFile=</path/to/java/security/properties/file> \
|
||||
-DdelayVerify=<true|false> \
|
||||
-DcertValidity=<[1, 1440]>] \
|
||||
<JDK_REPO>/jdk/test/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
<JDK_REPO>/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
|
||||
Besides the common jtreg options, like -jdk, this test introduces a set of
|
||||
properties for receiving users' inputs and making the test more flexible. These
|
||||
properties are:
|
||||
properties are (all to specify after -D as system properties):
|
||||
|
||||
proxyHost=<host>
|
||||
This property indicates proxy host.
|
||||
|
||||
proxyPort=<port>
|
||||
This property indicates proxy port. The default value is 80.
|
||||
|
||||
o=filename
|
||||
Redirects a copy of what is written to stdout into the specified file which
|
||||
allows for observing progress or problems during a longer running test.
|
||||
(Compatibility test replaces System.out to collect the output but even if
|
||||
it didn't jtreg would not print anything until the test would have ended.)
|
||||
Note that relative paths resolve relatively to some temporary working
|
||||
directory created by jtreg. Defaults to JTwork/scratch/details.out.
|
||||
The specified file is not deleted or emptied and the output is appended
|
||||
though jtreg deletes the default file with the whole directory if this
|
||||
option is not specified or the file point into JTwork/scratch.
|
||||
Example (Bash style): tail -F log & jtreg ... -Do=$(pwd)/log ...
|
||||
|
||||
tsaListFile=</path/to/tsaListFile>
|
||||
This property indicates a local file, which contains a set of TSA URLs and
|
||||
the supported digest algorithms (by optional parameter digests). The format
|
||||
@ -112,11 +143,12 @@ tsaListFile=</path/to/tsaListFile>
|
||||
on SHA-1, SHA-256 and SHA-512. So, if other digest algorithms, like SHA-224
|
||||
and SHA-384, are listed, they just be ignored.
|
||||
|
||||
tsaList=</path/to/tsa1#/path/to/tsa2;digests=SHA-1,SHA-256#...>
|
||||
tsaList=</path/to/tsa1#/path/to/tsa2;digests=SHA-1,SHA-256#...|notsa>
|
||||
This property directly lists a set of TSAs in command. "#" is the delimiter.
|
||||
Note that, if both of tsaListFile and tsaList are specified, only property
|
||||
jdkListFile is selected. If neither of tsaListFile and tsaList is specified,
|
||||
the test will fails immediately.
|
||||
tsaListFile is selected. If neither of tsaListFile and tsaList is specified,
|
||||
the test fails immediately.
|
||||
If tsaList has a value of "notsa", no tsa is used.
|
||||
|
||||
jdkListFile=</path/to/jdkListFile>
|
||||
This property indicates a local file, which contains a set of local JDK
|
||||
@ -129,11 +161,42 @@ jdkListFile=</path/to/jdkListFile>
|
||||
jdkList=</path/to/jdk1#/path/to/jdk2#/path/to/jdk3#...>
|
||||
This property directly lists a set of local JDK paths in command. "#" is
|
||||
the delimiter.
|
||||
An element "TEST_JDK" as in
|
||||
jdkList=</path/to/jdk1#/path/to/jdk2#TEST_JDK#...>
|
||||
adds the testing JDK, which is specified by jtreg option -jdk, to the jdk
|
||||
list. All signed jars are verified with the current testing JDK, which is
|
||||
specified by jtreg option -jdk, by default in addition to the JDKs given
|
||||
in jdkList but it is not used to also sign jars by default.
|
||||
If neither jdkList nor jdkListFile are specified, the current testing JDK,
|
||||
which is specified by jtreg option -jdk, is used to sign the jars, like:
|
||||
jdkList=TEST_JDK
|
||||
Note that, if both of jdkListFile and jdkList are specified, only property
|
||||
jdkListFile is selected. If neither of jdkListFile nor jdkList is specified,
|
||||
the testing JDK, which is specified by jtreg option -jdk will be used as
|
||||
the testing JDK, which is specified by jtreg option -jdk, will be used as
|
||||
the only one JDK in the JDK list.
|
||||
|
||||
The testing JDK, which is specified by jtreg option "-jdk", should include
|
||||
the fix for JDK-8163304. Otherwise, the signature algorithm and timestamp
|
||||
digest algorithm cannot be extracted from verification output. And this JDK
|
||||
should support as many as possible signature algorithms. Anyway the latest
|
||||
JDK build is always recommended.
|
||||
|
||||
testComprehensiveJarContents=<false|true>
|
||||
If false, all tests are executed with only one typical JAR file. Otherwise,
|
||||
if true, a whole bunch of JAR files with several edge case contents are
|
||||
fed through the tests such as empty manifest or manifests with non-default
|
||||
line breaks. Default is false.
|
||||
|
||||
testJarUpdate=<false|true>
|
||||
If false, all tested JAR files are signed with one JDK and verified with
|
||||
each JDK, same or other. If true, in addition, all JAR files are modified
|
||||
after having been signed, and are then each signed again with each JDK and
|
||||
verified each JDK, same or other. Default is false.
|
||||
|
||||
strict=<false|true>
|
||||
If true, '-strict' option is specified to jarsigner along with '-verify'.
|
||||
Default is false.
|
||||
|
||||
javaSecurityFile=</path/to/java/security/properties/file>
|
||||
This property indicates an alternative java security properties file. The
|
||||
default file is the path of file java.scurity that is distributed with
|
||||
@ -143,16 +206,36 @@ delayVerify=<true|false>
|
||||
This property indicates if doing an additional verifying after all of valid
|
||||
certificates expire. The default value is false.
|
||||
|
||||
expired=<false|true>
|
||||
This property indicates whether or not all tests should be repeated with an
|
||||
expired certificate. Refers to the certificate validity period and not to
|
||||
TSA. The default value is true.
|
||||
|
||||
certValidity=<[1, 1440]>
|
||||
This property indicates the remaining validity period in minutes for valid
|
||||
certificates. The value range is [1, 1440]. The default value is 1440.
|
||||
Note that, if delayVerify is false, this property doesn't take effect.
|
||||
|
||||
The testing JDK, which is specified by jtreg option "-jdk", should include the
|
||||
fix for JDK-8163304. Otherwise, the signature algorithm and timestamp digest
|
||||
algorithm cannot be extracted from verification output. And this JDK should
|
||||
support as many as possible signature algorithms. Anyway the latest JDK build
|
||||
is always recommended.
|
||||
keyAlgs=RSA;1024;2048;#DSA;1024;2048;#EC;384;521;
|
||||
Specifies key algorithms to use in the test. For each key algorithm the
|
||||
sizes it should be tested with can be specified after semicolons and
|
||||
otherwise default values are used. An empty keysize denotes the default
|
||||
keysize and invokes keytool without a keysize specified. On JDK 6 and
|
||||
earlier, EC is not supported and always skipped.
|
||||
|
||||
digestAlgs=SHA-1#SHA-256#SHA-384#SHA-512#
|
||||
Specifies the digest algorithms used for both digesting files contained in
|
||||
the JAR file, manifests and signature files as well as certificates (keys)
|
||||
and for TSA.
|
||||
Ignored with TSA for jarsigner versions that don't support '-tsadigestalg'
|
||||
parameter, for digest algorithms specified not to be supported by a TSA
|
||||
server ('digests' sub-option is given to a tsaList item where digest
|
||||
algorithm is not contained in list), or in cases no TSA is used at all
|
||||
('tsaList=notsa').
|
||||
Note that the same set of digest algorithms is used in all three places
|
||||
(signing the key, digesting the JAR, and for the TSA) and cannot be
|
||||
specified individually except that some TSAs may exclude some digest
|
||||
algorithms.
|
||||
|
||||
##### Examples #####
|
||||
$ cat /path/to/jdkList
|
||||
@ -177,13 +260,13 @@ http://tsa.swisssign.net
|
||||
http://zeitstempel.dfn.de
|
||||
https://tsp.iaik.tugraz.at/tsp/TspRequest
|
||||
|
||||
$ jtreg -va -nr -timeout:100 \
|
||||
$ jtreg -va -nr \
|
||||
-jdk:/path/to/latest/jdk \
|
||||
-DproxyHost=<proxy> -DproxyPort=<port> \
|
||||
-DjdkListFile=/path/to/jdkList \
|
||||
-DtsaListFile=/path/to/tsaList \
|
||||
-DdelayVerify=true -DcertValidity=60 \
|
||||
<JDK_REPO>/jdk/test/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
<JDK_REPO>/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
|
||||
The above is a comprehensive usage example. File "jdkList" lists the paths of
|
||||
testing JDK builds, and file "tsaList" lists the URLs of TSA services. Some TSAs,
|
||||
@ -197,19 +280,28 @@ of valid certificates expire and then does verification again.
|
||||
If don't want to provide such JDK list and TSA list files, the test allows to
|
||||
specify JDKs and TSAs (via properties jdkList and tsaList respectively) in the
|
||||
command directly, like the below style,
|
||||
$ jtreg -va -nr -timeout:100 \
|
||||
$ jtreg -va -nr \
|
||||
-jdk:/path/to/latest/jdk \
|
||||
-DproxyHost=<proxy> -DproxyPort=<port> \
|
||||
-DjdkList=/path/to/jdk6u171-b05#/path/to/jdk7u161-b05#/path/to/jdk8u144-b01#/path/to/jdk9-179 \
|
||||
-DtsaList=http://timestamp.comodoca.com/rfc3161#http://timestamp.entrust.net/TSS/RFC3161sha1TS;digests=SHA-1,SHA-256 \
|
||||
-DdelayVerify=true -DcertValidity=60 \
|
||||
<JDK_REPO>/jdk/test/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
<JDK_REPO>/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
|
||||
Furthermore, here introduces one of the simplest usages. It doesn't specify any
|
||||
JDK list, so the testing JDK, which is specified by jtreg option "-jdk", will
|
||||
be tested. And it doesn't apply delay verifying, and no proxy is used, and use
|
||||
only one TSA. Now, the command is pretty simple and looks like the followings,
|
||||
$ jtreg -va -nr -timeout:100 \
|
||||
$ jtreg -va -nr \
|
||||
-jdk:/path/to/latest/jdk \
|
||||
-DtsaList=http://timestamp.comodoca.com/rfc3161 \
|
||||
<JDK_REPO>/jdk/test/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
<JDK_REPO>/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
|
||||
It also works without a tsaList but not without the tsaList argument present
|
||||
in order to prevent it going missing or ignored unnoticed. May be useful for
|
||||
local tests but not recommended for real regression tests. Together with other
|
||||
arguments, a very short running test could be started for example with:
|
||||
$ jtreg -va -nr \
|
||||
-jdk:/path/to/latest/jdk \
|
||||
-DtsaList=notsa "-DkeyAlgs=EC;" -DdigestAlgs=SHA-256 -Dexpired=false
|
||||
<JDK_REPO>/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @bug 8217375
|
||||
* @summary This test runs those test cases of {@link Compatibility} test nearby
|
||||
* which can be executed within the currently built and tested JDK and without
|
||||
* TSA, with only one digest algorithm and with only one key (algorithm and
|
||||
* size) and without delayed verification.
|
||||
* Other test cases are to be executed manually invoking {@link Compatibility}
|
||||
* involving more than the currently built and tested JDK verifying the
|
||||
* compatibility of jarsigner across different JDK releases.
|
||||
* For more details about the test and its usages, please look at the README.
|
||||
*/
|
||||
/*
|
||||
* @test
|
||||
* @library /test/lib ../warnings
|
||||
* @compile Compatibility.java
|
||||
* @run main/othervm
|
||||
* -Djava.security.properties=./java.security
|
||||
* -Duser.language=en
|
||||
* -Duser.country=US
|
||||
* -DjdkList=TEST_JDK
|
||||
* -DtsaList=notsa
|
||||
* -Dexpired=false
|
||||
* -DtestComprehensiveJarContents=true
|
||||
* -DtestJarUpdate=true
|
||||
* -Dstrict=true
|
||||
* -DkeyAlgs=EC;#RSA;#DSA;
|
||||
* -DdigestAlgs=SHA-512
|
||||
* SignTwice
|
||||
*/
|
||||
public class SignTwice {
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
Compatibility.main(args);
|
||||
}
|
||||
|
||||
}
|
@ -61,12 +61,16 @@ public abstract class Test {
|
||||
static final int VALIDITY = 365;
|
||||
|
||||
static final String WARNING = "Warning:";
|
||||
static final String WARNING_OR_ERROR = "(Warning|Error):";
|
||||
static final String ERROR = "[Ee]rror:";
|
||||
static final String WARNING_OR_ERROR = "(" + WARNING + "|" + ERROR + ")";
|
||||
|
||||
static final String CHAIN_NOT_VALIDATED_VERIFYING_WARNING
|
||||
= "This jar contains entries "
|
||||
+ "whose certificate chain is invalid.";
|
||||
|
||||
static final String CERTIFICATE_SELF_SIGNED
|
||||
= "The signer's certificate is self-signed.";
|
||||
|
||||
static final String ALIAS_NOT_IN_STORE_VERIFYING_WARNING
|
||||
= "This jar contains signed entries "
|
||||
+ "that are not signed by alias in this keystore.";
|
||||
|
390
test/jdk/sun/security/util/ManifestDigester/DigestInput.java
Normal file
390
test/jdk/sun/security/util/ManifestDigester/DigestInput.java
Normal file
@ -0,0 +1,390 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.jar.Attributes.Name;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import sun.security.util.ManifestDigester;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Factory;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.AfterTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @modules java.base/sun.security.util
|
||||
* @compile ../../tools/jarsigner/Utils.java
|
||||
* @run testng DigestInput
|
||||
* @summary Checks that the manifest main attributes and entry digests are the
|
||||
* same as before resolution of bug 8217375 which means they treat some white
|
||||
* space different for oldStyle or digestWorkaround except for the blank line
|
||||
* at the end of the manifest file for digestWorkaround.
|
||||
*/
|
||||
public class DigestInput {
|
||||
|
||||
/**
|
||||
* Filters some test cases for calibrating expected digests with previous
|
||||
* implementation. TODO: Delete this after calibrating with old sources.
|
||||
*/
|
||||
static final boolean FIXED_8217375 = true; // FIXME
|
||||
|
||||
/**
|
||||
* {@link ManifestDigester.Entry#digestWorkaround} should not feed the
|
||||
* trailing blank line into the digester. Before resolution of 8217375 it
|
||||
* fed the trailing blank line into the digest if the second line break
|
||||
* was at the end of the file due to <pre>
|
||||
* if (allBlank || (i == len-1)) {
|
||||
* if (i == len-1)
|
||||
* pos.endOfSection = i;
|
||||
* else
|
||||
* pos.endOfSection = last;
|
||||
* </pre> in {@link ManifestDigester#findSection}. In that case at the end
|
||||
* of the manifest file, {@link ManifestDigester.Entry#digestWorkaround}
|
||||
* would have produced the same digest as
|
||||
* {@link ManifestDigester.Entry#digest} which was wrong and without effect
|
||||
* at best.
|
||||
* <p>
|
||||
* Once this fix is accepted, this flag can be removed along with
|
||||
* {@link #assertDigestEqualsCatchWorkaroundBroken}.
|
||||
*/
|
||||
static final boolean FIXED_8217375_EOF_ENDOFSECTION = FIXED_8217375;
|
||||
|
||||
static final String SECTION_NAME = "some individual section name";
|
||||
|
||||
@DataProvider(name = "parameters")
|
||||
public static Object[][] parameters() {
|
||||
List<Object[]> tests = new ArrayList<>();
|
||||
for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {
|
||||
if ("\r".equals(lineBreak) && !FIXED_8217375) continue;
|
||||
for (int addLB = 0; addLB <= 4; addLB++) {
|
||||
for (int numSecs = 0; numSecs <= 4; numSecs++) {
|
||||
for (boolean otherSec : new Boolean[] { false, true }) {
|
||||
for (boolean oldStyle : new Boolean[] { false, true }) {
|
||||
for (boolean workaround :
|
||||
new Boolean[] { false, true }) {
|
||||
tests.add(new Object[] {
|
||||
lineBreak, addLB, numSecs, otherSec,
|
||||
oldStyle, workaround
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tests.toArray(new Object[tests.size()][]);
|
||||
}
|
||||
|
||||
@Factory(dataProvider = "parameters")
|
||||
public static Object[] createTests(
|
||||
String lineBreak, int additionalLineBreaks,
|
||||
int numberOfSections, boolean hasOtherSection,
|
||||
boolean oldStyle, boolean digestWorkaround) {
|
||||
return new Object[] { new DigestInput(lineBreak,
|
||||
additionalLineBreaks, numberOfSections, hasOtherSection,
|
||||
oldStyle, digestWorkaround)
|
||||
};
|
||||
}
|
||||
|
||||
final String lineBreak;
|
||||
final int additionalLineBreaks; // number of blank lines delimiting section
|
||||
final int numberOfSections;
|
||||
final boolean hasOtherSection;
|
||||
final boolean oldStyle;
|
||||
final boolean digestWorkaround;
|
||||
|
||||
public DigestInput(
|
||||
String lineBreak, int additionalLineBreaks,
|
||||
int numberOfSections, boolean hasOtherSection,
|
||||
boolean oldStyle, boolean digestWorkaround) {
|
||||
this.lineBreak = lineBreak;
|
||||
this.additionalLineBreaks = additionalLineBreaks;
|
||||
this.numberOfSections = numberOfSections;
|
||||
this.hasOtherSection = hasOtherSection;
|
||||
this.oldStyle = oldStyle;
|
||||
this.digestWorkaround = digestWorkaround;
|
||||
}
|
||||
|
||||
@BeforeMethod
|
||||
public void verbose() {
|
||||
System.out.println("-".repeat(72));
|
||||
System.out.println("lineBreak = " +
|
||||
Utils.escapeStringWithNumbers(lineBreak));
|
||||
System.out.println("additionalLineBreaks = " + additionalLineBreaks);
|
||||
System.out.println("numberOfSections = " + numberOfSections);
|
||||
System.out.println("hasOtherSection = " + hasOtherSection);
|
||||
System.out.println("oldStyle = " + oldStyle);
|
||||
System.out.println("digestWorkaround = " + digestWorkaround);
|
||||
System.out.println("-".repeat(72));
|
||||
}
|
||||
|
||||
byte[] rawManifestBytes() {
|
||||
return (
|
||||
Name.MANIFEST_VERSION + ": 1.0" + lineBreak +
|
||||
"OldStyle0: no trailing space" + lineBreak +
|
||||
"OldStyle1: trailing space " + lineBreak +
|
||||
"OldStyle2: two trailing spaces " + lineBreak +
|
||||
lineBreak.repeat(additionalLineBreaks) +
|
||||
(
|
||||
"Name: " + SECTION_NAME + lineBreak +
|
||||
"OldStyle0: no trailing space" + lineBreak +
|
||||
"OldStyle1: trailing space " + lineBreak +
|
||||
"OldStyle2: two trailing spaces " + lineBreak +
|
||||
lineBreak.repeat(additionalLineBreaks)
|
||||
).repeat(numberOfSections) +
|
||||
(hasOtherSection ?
|
||||
"Name: unrelated trailing section" + lineBreak +
|
||||
"OldStyle0: no trailing space" + lineBreak +
|
||||
"OldStyle1: trailing space " + lineBreak +
|
||||
"OldStyle2: two trailing spaces " + lineBreak +
|
||||
lineBreak.repeat(additionalLineBreaks)
|
||||
: "")
|
||||
).getBytes(UTF_8);
|
||||
}
|
||||
|
||||
byte[] expectedMainAttrsDigest(boolean digestWorkaround) {
|
||||
return (
|
||||
Name.MANIFEST_VERSION + ": 1.0" + lineBreak +
|
||||
"OldStyle0: no trailing space" + lineBreak +
|
||||
"OldStyle1: trailing space" +
|
||||
(!oldStyle || !lineBreak.startsWith("\r") || digestWorkaround ?
|
||||
" " : "") + lineBreak +
|
||||
"OldStyle2: two trailing spaces " +
|
||||
(!oldStyle || !lineBreak.startsWith("\r") || digestWorkaround ?
|
||||
" " : "") + lineBreak +
|
||||
(
|
||||
(
|
||||
!digestWorkaround
|
||||
|| (
|
||||
additionalLineBreaks == 1
|
||||
&& numberOfSections == 0
|
||||
&& !hasOtherSection
|
||||
&& (
|
||||
digestWorkaround
|
||||
&& !FIXED_8217375_EOF_ENDOFSECTION
|
||||
)
|
||||
)
|
||||
) && (
|
||||
additionalLineBreaks > 0
|
||||
|| numberOfSections > 0
|
||||
|| hasOtherSection
|
||||
)
|
||||
? lineBreak : "")
|
||||
).getBytes(UTF_8);
|
||||
}
|
||||
|
||||
byte[] expectedIndividualSectionDigest(boolean digestWorkaround) {
|
||||
if (numberOfSections == 0) return null;
|
||||
return (
|
||||
(
|
||||
"Name: " + SECTION_NAME + lineBreak +
|
||||
"OldStyle0: no trailing space" + lineBreak +
|
||||
"OldStyle1: trailing space" +
|
||||
(!oldStyle || !lineBreak.startsWith("\r")
|
||||
|| digestWorkaround ? " " : "") + lineBreak +
|
||||
"OldStyle2: two trailing spaces " +
|
||||
(!oldStyle || !lineBreak.startsWith("\r")
|
||||
|| digestWorkaround ? " " : "") + lineBreak +
|
||||
(
|
||||
(
|
||||
!digestWorkaround
|
||||
) && (
|
||||
additionalLineBreaks > 0
|
||||
)
|
||||
? lineBreak : "")
|
||||
).repeat(numberOfSections) +
|
||||
(
|
||||
additionalLineBreaks == 1
|
||||
&& !hasOtherSection
|
||||
&& digestWorkaround
|
||||
&& !FIXED_8217375_EOF_ENDOFSECTION
|
||||
? lineBreak : "")
|
||||
).getBytes(UTF_8);
|
||||
}
|
||||
|
||||
class EchoMessageDigest extends MessageDigest {
|
||||
|
||||
ByteArrayOutputStream buf;
|
||||
|
||||
EchoMessageDigest() {
|
||||
super("echo");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineReset() {
|
||||
buf = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte input) {
|
||||
buf.write(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] i, int o, int l) {
|
||||
buf.write(i, o, l);
|
||||
}
|
||||
|
||||
@Override protected byte[] engineDigest() {
|
||||
return buf.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
byte[] digestMainAttributes(byte[] mfBytes) throws Exception {
|
||||
Utils.echoManifest(mfBytes, "going to digest main attributes of");
|
||||
|
||||
ManifestDigester md = new ManifestDigester(mfBytes);
|
||||
ManifestDigester.Entry entry =
|
||||
md.get(ManifestDigester.MF_MAIN_ATTRS, oldStyle);
|
||||
MessageDigest digester = new EchoMessageDigest();
|
||||
return digestWorkaround ?
|
||||
entry.digestWorkaround(digester) : entry.digest(digester);
|
||||
}
|
||||
|
||||
byte[] digestIndividualSection(byte[] mfBytes) throws Exception {
|
||||
Utils.echoManifest(mfBytes,
|
||||
"going to digest section " + SECTION_NAME + " of");
|
||||
|
||||
ManifestDigester md = new ManifestDigester(mfBytes);
|
||||
ManifestDigester.Entry entry = md.get(SECTION_NAME, oldStyle);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
MessageDigest digester = new EchoMessageDigest();
|
||||
return digestWorkaround ?
|
||||
entry.digestWorkaround(digester) : entry.digest(digester);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks that the manifest main attributes digest is the same as before.
|
||||
*/
|
||||
@Test
|
||||
public void testMainAttributesDigest() throws Exception {
|
||||
byte[] mfRaw = rawManifestBytes();
|
||||
byte[] digest = digestMainAttributes(mfRaw);
|
||||
byte[] expectedDigest = expectedMainAttrsDigest(digestWorkaround);
|
||||
|
||||
// the individual section will be digested along with the main
|
||||
// attributes if not properly delimited with a blank line
|
||||
if (additionalLineBreaks == 0
|
||||
&& (numberOfSections > 0 || hasOtherSection)) {
|
||||
assertNotEquals(digest, expectedDigest);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] expectedDigestNoWorkaround = expectedMainAttrsDigest(false);
|
||||
|
||||
// assertDigestEquals(digest, expectedDigest); // FIXME
|
||||
assertDigestEqualsCatchWorkaroundBroken(
|
||||
digest, expectedDigest, expectedDigestNoWorkaround);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that an individual section digest is the same as before.
|
||||
*/
|
||||
@Test
|
||||
public void testIndividualSectionDigest() throws Exception {
|
||||
byte[] mfRaw = rawManifestBytes();
|
||||
byte[] digest = digestIndividualSection(mfRaw);
|
||||
|
||||
// no digest will be produced for an individual section that is not
|
||||
// properly section delimited with a blank line.
|
||||
byte[] expectedDigest =
|
||||
additionalLineBreaks == 0 ? null :
|
||||
expectedIndividualSectionDigest(digestWorkaround);
|
||||
|
||||
byte[] expectedDigestNoWorkaround =
|
||||
additionalLineBreaks == 0 ? null :
|
||||
expectedIndividualSectionDigest(false);
|
||||
|
||||
// assertDigestEquals(digest, expectedDigest); // FIXME
|
||||
assertDigestEqualsCatchWorkaroundBroken(
|
||||
digest, expectedDigest, expectedDigestNoWorkaround);
|
||||
}
|
||||
|
||||
static int firstDiffPos = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* @see FIXED_8217375_EOF_ENDOFSECTION
|
||||
*/
|
||||
void assertDigestEqualsCatchWorkaroundBroken(
|
||||
byte[] actual, byte[] expected, byte[] expectedNoWorkaround)
|
||||
throws IOException {
|
||||
try {
|
||||
assertDigestEquals(actual, expected);
|
||||
} catch (AssertionError e) {
|
||||
if (digestWorkaround && FIXED_8217375_EOF_ENDOFSECTION &&
|
||||
Arrays.equals(expected, expectedNoWorkaround)) {
|
||||
// if digests with and without workaround are the same anyway
|
||||
// the workaround has failed and could not have worked with
|
||||
// the same digest as produced without workaround before
|
||||
// which would not match either because equal.
|
||||
return;
|
||||
}
|
||||
fail("failed also without digestWorkaound", e);
|
||||
}
|
||||
}
|
||||
|
||||
void assertDigestEquals(byte[] actual, byte[] expected) throws IOException {
|
||||
if (actual == null && expected == null) return;
|
||||
Utils.echoManifest(actual, "actual digest");
|
||||
Utils.echoManifest(expected, "expected digest");
|
||||
for (int i = 0; i < actual.length && i < expected.length; i++) {
|
||||
if (actual[i] != expected[i]) {
|
||||
firstDiffPos = Math.min(firstDiffPos, i);
|
||||
verbose();
|
||||
fail("found first difference in current test"
|
||||
+ " at position " + i);
|
||||
}
|
||||
}
|
||||
if (actual.length != expected.length) {
|
||||
int diffPos = Math.min(actual.length, expected.length);
|
||||
firstDiffPos = Math.min(firstDiffPos, diffPos);
|
||||
verbose();
|
||||
fail("found first difference in current test"
|
||||
+ " at position " + diffPos + " after one digest end");
|
||||
}
|
||||
assertEquals(actual, expected);
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
public void reportFirstDiffPos() {
|
||||
System.err.println("found first difference in all tests"
|
||||
+ " at position " + firstDiffPos);
|
||||
}
|
||||
|
||||
}
|
750
test/jdk/sun/security/util/ManifestDigester/FindSection.java
Normal file
750
test/jdk/sun/security/util/ManifestDigester/FindSection.java
Normal file
@ -0,0 +1,750 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import sun.security.util.ManifestDigester;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Factory;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @modules java.base/sun.security.util:+open
|
||||
* @compile ../../tools/jarsigner/Utils.java
|
||||
* @run testng/othervm FindSection
|
||||
* @summary Check {@link ManifestDigester#findSection}.
|
||||
*/
|
||||
public class FindSection {
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* FIXED_8217375 is not intended to keep. it is intended to show what
|
||||
* exactly has changed with respect to the previous version for which no
|
||||
* such test existed.
|
||||
*/
|
||||
static final boolean FIXED_8217375 = true;
|
||||
|
||||
/**
|
||||
* {@link ManifestDigester.Entry#digestWorkaround} should not feed the
|
||||
* trailing blank line into the digester. Before resolution of 8217375 it
|
||||
* fed the trailing blank line into the digest if the second line break
|
||||
* was at the end of the file due to <pre>
|
||||
* if (allBlank || (i == len-1)) {
|
||||
* if (i == len-1)
|
||||
* pos.endOfSection = i;
|
||||
* else
|
||||
* pos.endOfSection = last;
|
||||
* </pre> in {@link ManifestDigester#findSection}. In that case at the end
|
||||
* of the manifest file, {@link ManifestDigester.Entry#digestWorkaround}
|
||||
* would have produced the same digest as
|
||||
* {@link ManifestDigester.Entry#digest} which was wrong and without effect
|
||||
* at best.
|
||||
* <p>
|
||||
* Once this fix is accepted, this flag can be removed along with
|
||||
* {@link #actualEndOfSection8217375}.
|
||||
*/
|
||||
static final boolean FIXED_8217375_EOF_ENDOFSECTION = FIXED_8217375;
|
||||
|
||||
/**
|
||||
* {@link ManifestDigester.Position.endOfSection} usually points to the
|
||||
* start position of the blank line trailing a section minus one.
|
||||
* If a {@link ManifestDigester.Position} returned by
|
||||
* {@link ManifestDigester#findSection} is based on a portion that starts
|
||||
* with a blank line, above statement is (or was) not true, because of the
|
||||
* initialization of {@code last} in {@link ManifestDigester#findSection}
|
||||
* <pre>
|
||||
* int last = offset;
|
||||
* </pre>
|
||||
* which would point after incrementing it in {@code pos.endOfSection + 1}
|
||||
* on line 128 (line number before this change) or {@code int sectionLen =
|
||||
* pos.endOfSection-start+1;} on line 133 (line number before this change)
|
||||
* at one byte after the first line break character of usually two and
|
||||
* possibly (assuming "{@code \r\n}" default line break normally) in between
|
||||
* the two characters of a line break. After subtracting again the index of
|
||||
* the section start position on former line 133, the last byte would be
|
||||
* missed to be digested by {@link ManifestDigester.Entry#digestWorkaround}.
|
||||
* <p>
|
||||
* All this, however could possibly matter (or have mattered) only when
|
||||
* {@link ManifestDigester#findSection} was invoked with an offset position
|
||||
* pointing straight to a line break which happens if a manifest starts
|
||||
* with an empty line or if there are superfluous blank lines between
|
||||
* sections in both cases no useful manifest portion is identified.
|
||||
* Superfluous blank lines are not identified as sections (because they
|
||||
* don't have a name and specifically don't meet {@code if (len > 6) {} on
|
||||
* former line 136. Manifests starting with a line break are not any more
|
||||
* useful either.
|
||||
* <p>
|
||||
* Once this fix is accepted, this flag can be removed along with
|
||||
* {@link #actualEndOfSection8217375}.
|
||||
*/
|
||||
static final boolean FIXED_8217375_STARTWITHBLANKLINE_ENDOFSECTION =
|
||||
FIXED_8217375;
|
||||
|
||||
static Constructor<?> PositionConstructor;
|
||||
static Method findSection;
|
||||
static Field rawBytes;
|
||||
static Field endOfFirstLine;
|
||||
static Field endOfSection;
|
||||
static Field startOfNext;
|
||||
|
||||
@BeforeClass
|
||||
public static void setFindSectionAccessible() throws Exception {
|
||||
Class<?> Position = Arrays.stream(ManifestDigester.class.
|
||||
getDeclaredClasses()).filter(c -> c.getSimpleName().
|
||||
equals("Position")).findFirst().get();
|
||||
PositionConstructor = Position.getDeclaredConstructor();
|
||||
PositionConstructor.setAccessible(true);
|
||||
findSection = ManifestDigester.class.getDeclaredMethod("findSection",
|
||||
int.class, Position);
|
||||
findSection.setAccessible(true);
|
||||
rawBytes = ManifestDigester.class.getDeclaredField("rawBytes");
|
||||
rawBytes.setAccessible(true);
|
||||
endOfFirstLine = Position.getDeclaredField("endOfFirstLine");
|
||||
endOfFirstLine.setAccessible(true);
|
||||
endOfSection = Position.getDeclaredField("endOfSection");
|
||||
endOfSection.setAccessible(true);
|
||||
startOfNext = Position.getDeclaredField("startOfNext");
|
||||
startOfNext.setAccessible(true);
|
||||
}
|
||||
|
||||
static class Position {
|
||||
final int endOfFirstLine; // not including newline character
|
||||
|
||||
final int endOfSection; // end of section, not including the blank line
|
||||
// between sections
|
||||
final int startOfNext; // the start of the next section
|
||||
|
||||
Position(Object pos) throws ReflectiveOperationException {
|
||||
endOfFirstLine = FindSection.endOfFirstLine.getInt(pos);
|
||||
endOfSection = FindSection.endOfSection.getInt(pos);
|
||||
startOfNext = FindSection.startOfNext.getInt(pos);
|
||||
}
|
||||
}
|
||||
|
||||
Position findSection(byte[] manifestBytes)
|
||||
throws ReflectiveOperationException {
|
||||
ManifestDigester manDig = new ManifestDigester("\n\n".getBytes(UTF_8));
|
||||
FindSection.rawBytes.set(manDig, manifestBytes);
|
||||
Object pos = PositionConstructor.newInstance();
|
||||
Object result = findSection.invoke(manDig, offset, pos);
|
||||
if (Boolean.FALSE.equals(result)) {
|
||||
return null; // indicates findSection having returned false
|
||||
} else {
|
||||
return new Position(pos);
|
||||
}
|
||||
}
|
||||
|
||||
@DataProvider(name = "parameters")
|
||||
public static Object[][] parameters() {
|
||||
return new Object[][] { { 0 }, { 42 } };
|
||||
}
|
||||
|
||||
@Factory(dataProvider = "parameters")
|
||||
public static Object[] createTests(int offset) {
|
||||
return new Object[]{ new FindSection(offset) };
|
||||
}
|
||||
|
||||
final int offset;
|
||||
|
||||
FindSection(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@BeforeMethod
|
||||
public void verbose() {
|
||||
System.out.println("offset = " + offset);
|
||||
}
|
||||
|
||||
Position findSection(String manifestString)
|
||||
throws ReflectiveOperationException {
|
||||
byte[] manifestBytes = manifestString.getBytes(UTF_8);
|
||||
byte[] manifestWithOffset = new byte[manifestBytes.length + offset];
|
||||
System.arraycopy(manifestBytes, 0, manifestWithOffset, offset,
|
||||
manifestBytes.length);
|
||||
return findSection(manifestWithOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Surprising, but the offset actually makes a difference in
|
||||
* {@link ManifestDigester#findSection} return value.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
int actualEndOfFirstLine8217375(int correctPosition) {
|
||||
// if the parsed portion of the manifest starts with a blank line,
|
||||
// and offset is 0, "pos.endOfFirstLine = -1;" probably denoting a
|
||||
// yet uninitialized value coincides with the assignment by
|
||||
// "pos.endOfFirstLine = i-1;" if i == 0 and
|
||||
// "if (pos.endOfFirstLine == -1)" after "case '\n':" happens to
|
||||
// become true even though already assigned.
|
||||
if (offset == 0 && correctPosition == -1 && !FIXED_8217375) return 0;
|
||||
return correctPosition;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
int actualEndOfSection8217375(int correctPosition, boolean eof, int lbl) {
|
||||
// if the parsed portion of the manifest ends with a blank line and
|
||||
// just before eof, the blank line is included in Position.endOfSection/
|
||||
// Section.length (the one usually without blank line as well as in
|
||||
// Position.startOfNext/Section.lengthWithBlankLine) which is used
|
||||
// in digestWorkaround (independent of the digest without workaround)
|
||||
if (eof && !FIXED_8217375_EOF_ENDOFSECTION) {
|
||||
return correctPosition + lbl;
|
||||
} else if (correctPosition == -1
|
||||
&& !FIXED_8217375_STARTWITHBLANKLINE_ENDOFSECTION) {
|
||||
return 0;
|
||||
} else {
|
||||
return correctPosition;
|
||||
}
|
||||
}
|
||||
|
||||
AssertionError collectErrors(AssertionError a, Runnable run) {
|
||||
try {
|
||||
run.run();
|
||||
} catch (AssertionError e) {
|
||||
if (a == null) a = new AssertionError();
|
||||
a.addSuppressed(e);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
void assertPosition(Position pos,
|
||||
int endOfFirstLine, int endOfSection, int startOfNext) {
|
||||
AssertionError a = null;
|
||||
a = collectErrors(a, () -> assertEquals(
|
||||
pos.endOfFirstLine, endOfFirstLine + offset, "endOfFirstLine"));
|
||||
a = collectErrors(a, () -> assertEquals(
|
||||
pos.endOfSection, endOfSection + offset, "endOfSection"));
|
||||
a = collectErrors(a, () -> assertEquals(
|
||||
pos.startOfNext, startOfNext + offset, "startOfNext"));
|
||||
if (a != null) throw a;
|
||||
}
|
||||
|
||||
void catchCrCausesIndexOutOfBoundsException(
|
||||
Callable<Position> test, Consumer<Position> asserts) {
|
||||
try {
|
||||
Position x = test.call();
|
||||
if (!FIXED_8217375) fail();
|
||||
asserts.accept(x);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof IndexOutOfBoundsException ||
|
||||
e.getCause() instanceof IndexOutOfBoundsException) {
|
||||
if (FIXED_8217375) throw new AssertionError(e);
|
||||
} else {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpty() throws Exception {
|
||||
assertNull(findSection(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneLineBreakCr() throws Exception {
|
||||
catchCrCausesIndexOutOfBoundsException(
|
||||
() -> findSection("\r"),
|
||||
p -> assertPosition(p,
|
||||
-1, actualEndOfSection8217375(-1, false, 1), 1)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneLineBreakLf() throws Exception {
|
||||
assertPosition(findSection("\n"),
|
||||
-1, actualEndOfSection8217375(-1, false, 1), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneLineBreakCrLf() throws Exception {
|
||||
assertPosition(findSection("\r\n"),
|
||||
actualEndOfFirstLine8217375(-1),
|
||||
actualEndOfSection8217375(-1, true, 2),
|
||||
2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpaceAndLineBreakCr() throws Exception {
|
||||
catchCrCausesIndexOutOfBoundsException(
|
||||
() -> findSection(" \r"),
|
||||
p -> assertPosition(p, 2, 3, 4)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpaceAndOneLineBreakLf() throws Exception {
|
||||
assertPosition(findSection(" \n"), 2, 3, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpaceAndOneLineBreakCrLf() throws Exception {
|
||||
assertPosition(findSection(" \r\n"), 2, 4, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneLineBreakCrAndSpace() throws Exception {
|
||||
assertPosition(findSection("\r "),
|
||||
-1, actualEndOfSection8217375(-1, false, 1), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneLineBreakLfAndSpace() throws Exception {
|
||||
assertPosition(findSection("\n "),
|
||||
-1, actualEndOfSection8217375(-1, false, 1), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneLineBreakCrLfAndSpace() throws Exception {
|
||||
assertPosition(findSection("\r\n "),
|
||||
actualEndOfFirstLine8217375(-1),
|
||||
actualEndOfSection8217375(-1, false, 1),
|
||||
2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrEof() throws Exception {
|
||||
catchCrCausesIndexOutOfBoundsException(
|
||||
() -> findSection("abc\r"),
|
||||
p -> assertPosition(p, 2, 3, 4)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\n"), 2, 3, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\n"), 2, 4, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrContinued() throws Exception {
|
||||
assertPosition(findSection("abc\rxyz\r\n\r\n "), 2, 8, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\nxyz\r\n\r\n "), 2, 8, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrCrEof() throws Exception {
|
||||
catchCrCausesIndexOutOfBoundsException(
|
||||
() -> findSection("abc\r\nxyz\r\r"),
|
||||
p -> assertPosition(p,
|
||||
2, actualEndOfSection8217375(8, true, 1), 10)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrCrContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\r "), 2, 8, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLfLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\n\n"),
|
||||
2, actualEndOfSection8217375(8, true, 1), 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLfLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\n\n "), 2, 8, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfEof2() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n"), 2, 9, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMainSectionNotTerminatedWithLineBreak() throws Exception {
|
||||
assertNull(findSection("abc\r\nxyz\r\n "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLfCrEof() throws Exception {
|
||||
catchCrCausesIndexOutOfBoundsException(
|
||||
() -> findSection("abc\r\nxyz\n\r"),
|
||||
p -> assertPosition(p,
|
||||
2, actualEndOfSection8217375(8, true, 1), 10)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLfCrContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\n\r "), 2, 8, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrEof() throws Exception {
|
||||
catchCrCausesIndexOutOfBoundsException(
|
||||
() -> findSection("abc\r\nxyz\r\n\r"),
|
||||
p -> assertPosition(p,
|
||||
2, actualEndOfSection8217375(9, true, 2), 11)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r "), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n"),
|
||||
2, actualEndOfSection8217375(9, true, 1), 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n "), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n"),
|
||||
2, actualEndOfSection8217375(9, true, 2), 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCfLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r "), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r "), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r"), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCfLfCrContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r "), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n "), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\n "), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\n "), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n "), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n "), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n"), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCfLfCrLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n "), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r\r"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfCrCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\r"), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfCrContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r "), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfLfCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\n\r"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrLfCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\r"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfLfLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\n\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrLfLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r\r\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfCrCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\r\n"), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrLfCrLfContinued() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\n\r\n "), 2, 9, 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfLfCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\n\r\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfLfCrLfCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\n\r\n\r\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrLfCrCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\r"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrCrCrEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r\r"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrLfLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrLfCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r\n\r\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrLfCrCrCrLfEof() throws Exception {
|
||||
assertPosition(findSection("abc\r\nxyz\r\n\r\r\r\n"), 2, 9, 11);
|
||||
}
|
||||
|
||||
/*
|
||||
* endOfFirstLine is the same regardless of the line break delimiter
|
||||
*/
|
||||
@Test
|
||||
public void testEndOfFirstLineVsLineBreak() throws Exception {
|
||||
for (String lb : new String[] { "\r", "\n", "\r\n" }) {
|
||||
Position p = findSection("abc" + lb + "xyz" + lb + lb + " ");
|
||||
|
||||
// main assertion showing endOfFirstLine independent of line break
|
||||
assertEquals(p.endOfFirstLine, 2 + offset);
|
||||
|
||||
// assert remaining positions as well just for completeness
|
||||
assertPosition(p, 2, 5 + 2 * lb.length(), 6 + 3 * lb.length());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* '\r' at the end of the bytes causes index out of bounds exception
|
||||
*/
|
||||
@Test
|
||||
public void testCrLastCausesIndexOutOfBounds() throws Exception {
|
||||
catchCrCausesIndexOutOfBoundsException(
|
||||
() -> findSection("\r"),
|
||||
p -> assertPosition(p,
|
||||
-1, actualEndOfSection8217375(-1, true, 1), 1)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* endOfSection includes second line break if at end of bytes only
|
||||
*/
|
||||
@Test
|
||||
public void testEndOfSectionWithLineBreakVsEof() throws Exception {
|
||||
AssertionError errors = new AssertionError("offset = " + offset);
|
||||
for (String lb : new String[] { "\r", "\n", "\r\n" }) {
|
||||
for (boolean eof : new boolean[] { false, true }) {
|
||||
Position p;
|
||||
try {
|
||||
p = findSection("abc" + lb + lb + (eof ? "" : "xyz"));
|
||||
} catch (RuntimeException | ReflectiveOperationException e) {
|
||||
if ((e instanceof IndexOutOfBoundsException ||
|
||||
e.getCause() instanceof IndexOutOfBoundsException)
|
||||
&& eof && "\r".equals(lb) && !FIXED_8217375) continue;
|
||||
throw e;
|
||||
}
|
||||
|
||||
AssertionError a = new AssertionError("offset = " + offset
|
||||
+ ", lb = " + Utils.escapeStringWithNumbers(lb) + ", "
|
||||
+ "eof = " + eof);
|
||||
|
||||
// main assertion showing endOfSection including second line
|
||||
// break when at end of file
|
||||
a = collectErrors(a, () -> assertEquals(
|
||||
p.endOfSection,
|
||||
actualEndOfSection8217375(
|
||||
2 + lb.length() + offset, eof, lb.length()) ));
|
||||
|
||||
// assert remaining positions as well just for completeness
|
||||
a = collectErrors(a, () -> assertPosition(p,
|
||||
2,
|
||||
actualEndOfSection8217375(
|
||||
2 + lb.length(), eof, lb.length()),
|
||||
3 + lb.length() * 2));
|
||||
|
||||
if (a.getSuppressed().length > 0) errors.addSuppressed(a);
|
||||
}
|
||||
}
|
||||
if (errors.getSuppressed().length > 0) throw errors;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns position even if only one line break before end of bytes.
|
||||
* because no name will be found the result will be skipped and no entry
|
||||
* will be created.
|
||||
*/
|
||||
@Test
|
||||
public void testReturnPosVsEof() throws Exception {
|
||||
for (String lb : new String[] { "\r", "\n", "\r\n" }) {
|
||||
for (boolean eof : new boolean[] { false, true }) {
|
||||
try {
|
||||
Position p = findSection("abc" + lb + (eof ? "" : "xyz"));
|
||||
assertTrue(p != null == eof);
|
||||
} catch (RuntimeException | ReflectiveOperationException e) {
|
||||
if ((e instanceof IndexOutOfBoundsException ||
|
||||
e.getCause() instanceof IndexOutOfBoundsException)
|
||||
&& eof && "\r".equals(lb) && !FIXED_8217375) continue;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* it could be normally be expected that startOfNext would point to the
|
||||
* start of the next section after a blank line but that is not the case
|
||||
* if a section ends with only one line break and no blank line immediately
|
||||
* before eof of the manifest.
|
||||
* such an entry will be digested without the trailing blank line which is
|
||||
* only fine until another section should be added afterwards.
|
||||
*/
|
||||
@Test
|
||||
public void testStartOfNextPointsToEofWithNoBlankLine() throws Exception {
|
||||
for (String lb : new String[] { "\r", "\n", "\r\n" }) {
|
||||
for (boolean blank : new boolean[] { false, true }) {
|
||||
String manifest = "abc" + lb + "xyz" + lb + (blank ? lb : "");
|
||||
try {
|
||||
Position p = findSection(manifest);
|
||||
|
||||
// assert that startOfNext points to eof in all cases
|
||||
// whether with or without a blank line before eof
|
||||
assertEquals(p.startOfNext, manifest.length() + offset);
|
||||
|
||||
// assert remaining positions as well just for completeness
|
||||
assertPosition(p,
|
||||
2,
|
||||
actualEndOfSection8217375(
|
||||
5 + lb.length() * 2,
|
||||
true,
|
||||
blank ? lb.length() : 0),
|
||||
manifest.length());
|
||||
} catch (RuntimeException | ReflectiveOperationException e) {
|
||||
if ((e instanceof IndexOutOfBoundsException ||
|
||||
e.getCause() instanceof IndexOutOfBoundsException)
|
||||
&& "\r".equals(lb) && !FIXED_8217375) continue;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
156
test/jdk/sun/security/util/ManifestDigester/FindSections.java
Normal file
156
test/jdk/sun/security/util/ManifestDigester/FindSections.java
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import sun.security.util.ManifestDigester;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @modules java.base/sun.security.util
|
||||
* @run testng FindSections
|
||||
* @summary Check {@link ManifestDigester#ManifestDigester} processing
|
||||
* individual sections and particularly identifying their names correctly.
|
||||
* Main attributes are not covered in this test.
|
||||
* <p>
|
||||
* See also {@link FindSection} for the {@link ManifestDigester#findSection}
|
||||
* method specifically.
|
||||
*/
|
||||
public class FindSections {
|
||||
|
||||
static final String DEFAULT_MANIFEST = "Manifest-Version: 1.0\r\n\r\n";
|
||||
|
||||
void test(String manifest, String expectedSection) {
|
||||
ManifestDigester md = new ManifestDigester(manifest.getBytes(UTF_8));
|
||||
if (expectedSection != null) {
|
||||
assertNotNull(md.get(expectedSection, false));
|
||||
} else {
|
||||
assertNull(md.get(expectedSection, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameNoSpaceAfterColon() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name:section\r\n\r\n", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameCase() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "nAME: section\r\n\r\n", "section");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyName() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: \r\n\r\n", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortestInvalidSection() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: ", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinimalValidSection() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: \r", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameNotContinued() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: FooBar\r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmediatelyContinuedCrName() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: \r FooBar\r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmediatelyContinuedLfName() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: \n FooBar\r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmediatelyContinuedCrLfName() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: \r\n FooBar\r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedCr() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: FooBar\r \r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedLf() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: FooBar\n \r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedCrLf() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: FooBar\r\n \r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedCrIgnoreNextChar() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: Foo\r: Bar\r\n", "Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedCrIgnoreNextCharSpace() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: Foo\r Bar\r\n", "Foo Bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedContinuedCr() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: Fo\r\n oB\r ar\r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedContinuedLf() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: Fo\r\n oB\n ar\r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedContinuedCrLf() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: Fo\r\n oB\r\n ar\r\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedEndCr() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: Foo\r\n Bar\r", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedEndLf() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: Foo\r\n Bar\n", "FooBar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameContinuedEndCrLf() throws Exception {
|
||||
test(DEFAULT_MANIFEST + "Name: Foo\r\n Bar\r\n", "FooBar");
|
||||
}
|
||||
|
||||
}
|
142
test/jdk/sun/security/util/ManifestDigester/LineBreaks.java
Normal file
142
test/jdk/sun/security/util/ManifestDigester/LineBreaks.java
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import sun.security.util.ManifestDigester;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @modules java.base/sun.security.util
|
||||
* @compile ../../tools/jarsigner/Utils.java
|
||||
* @run testng LineBreaks
|
||||
* @summary Verify {@code ManifestDigester} reads different line breaks well.
|
||||
* The specifications state:
|
||||
* <q><pre>newline: CR LF | LF | CR (not followed by LF)</pre></q>.
|
||||
* This test does not verify that the digests are correct.
|
||||
*/
|
||||
public class LineBreaks {
|
||||
|
||||
static final String KEY = "Key";
|
||||
static final String VALUE = "Value";
|
||||
static final String SECTION = "Section";
|
||||
static final String FOO = "Foo";
|
||||
static final String BAR = "Bar";
|
||||
|
||||
static final String EXCEED_LINE_WIDTH_LIMIT = "x".repeat(71);
|
||||
|
||||
String breakAndContinue(String str, String lineBreak) {
|
||||
// assert no multi-byte UTF-8 encoded characters in this test
|
||||
assertEquals(str.getBytes(UTF_8).length, str.length());
|
||||
|
||||
int p = 1;
|
||||
while (p + 71 < str.length()) {
|
||||
p += 71;
|
||||
str = str.substring(0, p) + lineBreak + " " + str.substring(p);
|
||||
p += lineBreak.length() + 1;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
byte[] createTestManifest(String lineBreak, boolean onlyMainAttrs,
|
||||
String excess) throws IOException {
|
||||
System.out.println("lineBreak = "
|
||||
+ Utils.escapeStringWithNumbers(lineBreak));
|
||||
System.out.println("onlyMainAttrs = " + onlyMainAttrs);
|
||||
String mf = "";
|
||||
mf += breakAndContinue(
|
||||
KEY + ": " + VALUE + excess, lineBreak) + lineBreak;
|
||||
mf += lineBreak;
|
||||
if (!onlyMainAttrs) {
|
||||
mf += breakAndContinue(
|
||||
"Name: " + SECTION + excess, lineBreak) + lineBreak;
|
||||
mf += breakAndContinue(
|
||||
FOO + ": " + BAR + excess, lineBreak) + lineBreak;
|
||||
mf += lineBreak;
|
||||
}
|
||||
byte[] mfBytes = mf.getBytes(UTF_8);
|
||||
Utils.echoManifest(mfBytes, "binary manifest");
|
||||
return mfBytes;
|
||||
}
|
||||
|
||||
@DataProvider(name = "parameters")
|
||||
public static Object[][] parameters() {
|
||||
List<Object[]> tests = new ArrayList<>();
|
||||
for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {
|
||||
for (boolean onlyMainAttrs : new boolean[] { false, true }) {
|
||||
for (int numLbs = 0; numLbs < 3; numLbs++) {
|
||||
tests.add(new Object[]{ lineBreak, onlyMainAttrs, numLbs });
|
||||
}
|
||||
}
|
||||
}
|
||||
return tests.toArray(new Object[tests.size()][]);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "parameters")
|
||||
public void test(String lineBreak, boolean onlyMainAttrs, int numLbs)
|
||||
throws IOException {
|
||||
String excess = EXCEED_LINE_WIDTH_LIMIT.repeat(numLbs);
|
||||
byte[] mfBytes = createTestManifest(lineBreak, onlyMainAttrs, excess);
|
||||
|
||||
// self-test: make sure the manifest is valid and represents the
|
||||
// values as expected before attempting to digest it
|
||||
Manifest mf = new Manifest(new ByteArrayInputStream(mfBytes));
|
||||
assertEquals(mf.getMainAttributes().getValue(KEY), VALUE + excess);
|
||||
Attributes section = mf.getAttributes(SECTION + excess);
|
||||
if (onlyMainAttrs) {
|
||||
assertNull(section);
|
||||
} else {
|
||||
assertEquals(section.getValue(FOO), BAR + excess);
|
||||
}
|
||||
|
||||
// verify that ManifestDigester has actually found the individual
|
||||
// section if and only if it was present thereby also implying based
|
||||
// on ManifestDigester implementation that the main attributes were
|
||||
// found before
|
||||
ManifestDigester md = new ManifestDigester(mfBytes);
|
||||
assertTrue((md.get(SECTION + excess, false) != null) != onlyMainAttrs);
|
||||
}
|
||||
|
||||
static List<Integer> stringToIntList(String string) {
|
||||
byte[] bytes = string.getBytes(UTF_8);
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
list.add((int) bytes[i]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
324
test/jdk/sun/security/util/ManifestDigester/ReproduceRaw.java
Normal file
324
test/jdk/sun/security/util/ManifestDigester/ReproduceRaw.java
Normal file
@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import sun.security.util.ManifestDigester;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Factory;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8217375
|
||||
* @modules java.base/sun.security.util
|
||||
* @compile ../../tools/jarsigner/Utils.java
|
||||
* @run testng ReproduceRaw
|
||||
* @summary Verifies that {@link ManifestDigester} can reproduce parts of
|
||||
* manifests in their binary form so that {@link JarSigner} can rely on
|
||||
* {@link ManifestDigester.Entry#reproduceRaw} to write in a map view
|
||||
* unmodified entries back also unmodified in their binary form.
|
||||
* <p>
|
||||
* See also<ul>
|
||||
* <li>{@link PreserveRawManifestEntryAndDigest} with end to end tests
|
||||
* with {@code jarsigner} tool and</li>
|
||||
* <li>{@link FindHeaderEndVsManifestDigesterFindFirstSection} about
|
||||
* identifying the binary portion of only main attributes and more extensive
|
||||
* main attributes digesting tests while this one test here is more about
|
||||
* reproducing individual sections and that they result in the same
|
||||
* digests.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ReproduceRaw {
|
||||
|
||||
static final boolean VERBOSE = false;
|
||||
|
||||
@DataProvider(name = "parameters")
|
||||
public static Object[][] parameters() {
|
||||
List<Object[]> tests = new ArrayList<>();
|
||||
for (String lineBreak : new String[] { "\n", "\r", "\r\n" }) {
|
||||
for (boolean oldStyle : new Boolean[] { false, true }) {
|
||||
for (boolean workaround : new Boolean[] { false, true }) {
|
||||
tests.add(new Object[] { lineBreak, oldStyle, workaround });
|
||||
}
|
||||
}
|
||||
}
|
||||
return tests.toArray(new Object[tests.size()][]);
|
||||
}
|
||||
|
||||
@Factory(dataProvider = "parameters")
|
||||
public static Object[] createTests(String lineBreak,
|
||||
boolean oldStyle, boolean digestWorkaround) {
|
||||
return new Object[]{
|
||||
new ReproduceRaw(lineBreak, oldStyle, digestWorkaround)
|
||||
};
|
||||
}
|
||||
|
||||
final String lineBreak;
|
||||
final boolean oldStyle;
|
||||
final boolean digestWorkaround;
|
||||
|
||||
public ReproduceRaw(String lineBreak,
|
||||
boolean oldStyle, boolean digestWorkaround) {
|
||||
this.lineBreak = lineBreak;
|
||||
this.oldStyle = oldStyle;
|
||||
this.digestWorkaround = digestWorkaround;
|
||||
}
|
||||
|
||||
@BeforeMethod
|
||||
public void verbose() {
|
||||
System.out.println("lineBreak = " +
|
||||
Utils.escapeStringWithNumbers(lineBreak));
|
||||
System.out.println("oldStyle = " + oldStyle);
|
||||
System.out.println("digestWorkaround = " + digestWorkaround);
|
||||
}
|
||||
|
||||
class EchoMessageDigest extends MessageDigest {
|
||||
|
||||
ByteArrayOutputStream buf;
|
||||
|
||||
EchoMessageDigest() {
|
||||
super("echo");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineReset() {
|
||||
buf = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte input) {
|
||||
buf.write(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] i, int o, int l) {
|
||||
buf.write(i, o, l);
|
||||
}
|
||||
|
||||
@Override protected byte[] engineDigest() {
|
||||
return buf.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* similar to corresponding part of {@link JarSigner#sign0}
|
||||
* (stripped down to the code for reproducing the old manifest entry by
|
||||
* entry which was too difficult to achieve using the real JarSigner code
|
||||
* in the test here)
|
||||
*/
|
||||
byte[] reproduceRawManifest(byte[] mfRawBytes,
|
||||
boolean mainAttsProperlyDelimited,
|
||||
boolean sectionProperlyDelimited) throws IOException {
|
||||
Manifest manifest = new Manifest(new ByteArrayInputStream(mfRawBytes));
|
||||
ManifestDigester oldMd = new ManifestDigester(mfRawBytes);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
// main attributes
|
||||
assertEquals(oldMd.getMainAttsEntry().isProperlyDelimited(),
|
||||
mainAttsProperlyDelimited);
|
||||
oldMd.getMainAttsEntry().reproduceRaw(baos);
|
||||
|
||||
// individual sections
|
||||
for (String key : manifest.getEntries().keySet()) {
|
||||
assertEquals(oldMd.get(key).isProperlyDelimited(),
|
||||
sectionProperlyDelimited);
|
||||
oldMd.get(key).reproduceRaw(baos);
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
static String regExscape(String expr) {
|
||||
for (int i = 0; i < expr.length(); i++) {
|
||||
if (expr.charAt(i) == '\r' || expr.charAt(i) == '\n') {
|
||||
expr = expr.substring(0, i) + "\\" + expr.substring(i++);
|
||||
}
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
byte[] digest(byte[] manifest, String section) {
|
||||
MessageDigest digester = new EchoMessageDigest();
|
||||
ManifestDigester md = new ManifestDigester(manifest);
|
||||
ManifestDigester.Entry entry = section == null ?
|
||||
md.getMainAttsEntry(oldStyle) : md.get(section, oldStyle);
|
||||
return digestWorkaround ?
|
||||
entry.digestWorkaround(digester) :
|
||||
entry.digest(digester);
|
||||
}
|
||||
|
||||
void test(byte[] originalManifest, boolean mainAttsProperlyDelimited,
|
||||
boolean sectionProperlyDelimited) throws Exception {
|
||||
Utils.echoManifest(originalManifest, "original manifest");
|
||||
byte[] reproducedManifest = reproduceRawManifest(originalManifest,
|
||||
mainAttsProperlyDelimited, sectionProperlyDelimited);
|
||||
Utils.echoManifest(reproducedManifest, "reproduced manifest");
|
||||
|
||||
// The reproduced manifest is not necessarily completely identical to
|
||||
// the original if it contained superfluous blank lines.
|
||||
// It's sufficient that the digests are equal and as an additional
|
||||
// check, the reproduced manifest is here compared to the original
|
||||
// without more than double line breaks.
|
||||
if (!lineBreak.repeat(2).equals(new String(originalManifest, UTF_8))) {
|
||||
assertEquals(
|
||||
new String(reproducedManifest, UTF_8),
|
||||
new String(originalManifest, UTF_8).replaceAll(
|
||||
regExscape(lineBreak) + "(" + regExscape(lineBreak) + ")+",
|
||||
lineBreak.repeat(2)));
|
||||
}
|
||||
|
||||
// compare digests of reproduced manifest entries with digests of
|
||||
// original manifest entries
|
||||
assertEquals(digest(originalManifest, null),
|
||||
digest(reproducedManifest, null));
|
||||
for (String key : new Manifest(new ByteArrayInputStream(
|
||||
originalManifest)).getEntries().keySet()) {
|
||||
assertEquals(digest(originalManifest, key),
|
||||
digest(reproducedManifest, key));
|
||||
}
|
||||
|
||||
// parse and compare original and reproduced manifests as manifests
|
||||
assertEquals(new Manifest(new ByteArrayInputStream(originalManifest)),
|
||||
new Manifest(new ByteArrayInputStream(reproducedManifest)));
|
||||
}
|
||||
|
||||
void test(byte[] originalManifest, boolean mainAttsProperlyDelimited)
|
||||
throws Exception {
|
||||
// assert all individual sections properly delimited particularly useful
|
||||
// when no individual sections present
|
||||
test(originalManifest, mainAttsProperlyDelimited, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManifestStartsWithBlankLine() throws Exception {
|
||||
test(lineBreak.getBytes(UTF_8), true);
|
||||
test(lineBreak.repeat(2).getBytes(UTF_8), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEOFAndNoLineBreakAfterMainAttributes() throws Exception {
|
||||
assertThrows(RuntimeException.class, () ->
|
||||
test("Manifest-Version: 1.0".getBytes(UTF_8), false)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEOFAndNoBlankLineAfterMainAttributes() throws Exception {
|
||||
test(("Manifest-Version: 1.0" + lineBreak).getBytes(UTF_8), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalMainAttributes() throws Exception {
|
||||
test(("Manifest-Version: 1.0" +
|
||||
lineBreak.repeat(2)).getBytes(UTF_8), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtraLineBreakAfterMainAttributes() throws Exception {
|
||||
test(("Manifest-Version: 1.0" +
|
||||
lineBreak.repeat(3)).getBytes(UTF_8), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndividualSectionNoLineBreak() throws Exception {
|
||||
assertNull(new ManifestDigester((
|
||||
"Manifest-Version: 1.0" + lineBreak +
|
||||
lineBreak +
|
||||
"Name: Section-Name" + lineBreak +
|
||||
"Key: Value"
|
||||
).getBytes(UTF_8)).get("Section-Name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndividualSectionOneLineBreak() throws Exception {
|
||||
test((
|
||||
"Manifest-Version: 1.0" + lineBreak +
|
||||
lineBreak +
|
||||
"Name: Section-Name" + lineBreak +
|
||||
"Key: Value" + lineBreak
|
||||
).getBytes(UTF_8), true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalIndividualSectionTwoLineBreak() throws Exception {
|
||||
test((
|
||||
"Manifest-Version: 1.0" + lineBreak +
|
||||
lineBreak +
|
||||
"Name: Section-Name" + lineBreak +
|
||||
"Key: Value" + lineBreak.repeat(2)
|
||||
).getBytes(UTF_8), true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtraLineBreakAfterIndividualSection() throws Exception {
|
||||
test((
|
||||
"Manifest-Version: 1.0" + lineBreak +
|
||||
lineBreak +
|
||||
"Name: Section-Name" + lineBreak +
|
||||
"Key: Value" + lineBreak.repeat(3)
|
||||
).getBytes(UTF_8), true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndividualSections() throws Exception {
|
||||
test((
|
||||
"Manifest-Version: 1.0" + lineBreak +
|
||||
lineBreak +
|
||||
"Name: Section-Name" + lineBreak +
|
||||
"Key: Value" + lineBreak +
|
||||
lineBreak +
|
||||
"Name: Section-Name" + lineBreak +
|
||||
"Key: Value" + lineBreak +
|
||||
lineBreak
|
||||
).getBytes(UTF_8), true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtraLineBreakBetweenIndividualSections() throws Exception {
|
||||
test((
|
||||
"Manifest-Version: 1.0" + lineBreak +
|
||||
lineBreak +
|
||||
"Name: Section-Name" + lineBreak +
|
||||
"Key: Value" + lineBreak +
|
||||
lineBreak.repeat(2) +
|
||||
"Name: Section-Name" + lineBreak +
|
||||
"Key: Value" + lineBreak +
|
||||
lineBreak
|
||||
).getBytes(UTF_8), true, true);
|
||||
}
|
||||
|
||||
}
|
@ -635,10 +635,10 @@ public final class OutputAnalyzer {
|
||||
* just a subset of it.
|
||||
*
|
||||
* @param from
|
||||
* The line from where output will be matched.
|
||||
* The line (excluded) from where output will be matched.
|
||||
* Set {@code from} to null for matching from the first line.
|
||||
* @param to
|
||||
* The line until where output will be matched.
|
||||
* The line (excluded) until where output will be matched.
|
||||
* Set {@code to} to null for matching until the last line.
|
||||
* @param pattern
|
||||
* Matching pattern
|
||||
@ -653,10 +653,10 @@ public final class OutputAnalyzer {
|
||||
* just a subset of it.
|
||||
*
|
||||
* @param from
|
||||
* The line from where stdout will be matched.
|
||||
* The line (excluded) from where stdout will be matched.
|
||||
* Set {@code from} to null for matching from the first line.
|
||||
* @param to
|
||||
* The line until where stdout will be matched.
|
||||
* The line (excluded) until where stdout will be matched.
|
||||
* Set {@code to} to null for matching until the last line.
|
||||
* @param pattern
|
||||
* Matching pattern
|
||||
@ -670,25 +670,26 @@ public final class OutputAnalyzer {
|
||||
|
||||
int fromIndex = 0;
|
||||
if (from != null) {
|
||||
fromIndex = indexOf(lines, from);
|
||||
Asserts.assertGreaterThan(fromIndex, -1,
|
||||
fromIndex = indexOf(lines, from, 0) + 1; // + 1 -> apply 'pattern' to lines after 'from' match
|
||||
Asserts.assertGreaterThan(fromIndex, 0,
|
||||
"The line/pattern '" + from + "' from where the output should match can not be found");
|
||||
}
|
||||
|
||||
int toIndex = lines.size();
|
||||
if (to != null) {
|
||||
toIndex = indexOf(lines, to);
|
||||
Asserts.assertGreaterThan(toIndex, -1,
|
||||
toIndex = indexOf(lines, to, fromIndex);
|
||||
Asserts.assertGreaterThan(toIndex, fromIndex,
|
||||
"The line/pattern '" + to + "' until where the output should match can not be found");
|
||||
}
|
||||
|
||||
List<String> subList = lines.subList(fromIndex, toIndex);
|
||||
Asserts.assertFalse(subList.isEmpty(), "There are no lines to check");
|
||||
Asserts.assertFalse(subList.isEmpty(), "There are no lines to check:"
|
||||
+ " range " + fromIndex + ".." + toIndex + ", subList = " + subList);
|
||||
|
||||
subList.stream()
|
||||
.filter(Pattern.compile(pattern).asPredicate().negate())
|
||||
.findAny()
|
||||
.ifPresent(line -> Asserts.assertTrue(false,
|
||||
.ifPresent(line -> Asserts.fail(
|
||||
"The line '" + line + "' does not match pattern '" + pattern + "'"));
|
||||
|
||||
return this;
|
||||
@ -698,11 +699,12 @@ public final class OutputAnalyzer {
|
||||
* Check if there is a line matching {@code regexp} and return its index
|
||||
*
|
||||
* @param regexp Matching pattern
|
||||
* @param fromIndex Start matching after so many lines skipped
|
||||
* @return Index of first matching line
|
||||
*/
|
||||
private int indexOf(List<String> lines, String regexp) {
|
||||
private int indexOf(List<String> lines, String regexp, int fromIndex) {
|
||||
Pattern pattern = Pattern.compile(regexp);
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
for (int i = fromIndex; i < lines.size(); i++) {
|
||||
if (pattern.matcher(lines.get(i)).matches()) {
|
||||
return i;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user