View Javadoc
1   package org.eclipse.aether.util.repository;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   * 
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Arrays;
23  import java.util.Map;
24  import java.util.Objects;
25  
26  import static java.util.Objects.requireNonNull;
27  
28  import org.eclipse.aether.repository.Authentication;
29  import org.eclipse.aether.repository.AuthenticationContext;
30  import org.eclipse.aether.repository.AuthenticationDigest;
31  
32  /**
33   * Authentication block that manages a single authentication key and its secret string value (password, passphrase).
34   * Unlike {@link StringAuthentication}, the string value is kept in an encrypted buffer and only decrypted when needed
35   * to reduce the potential of leaking the secret in a heap dump.
36   */
37  final class SecretAuthentication
38      implements Authentication
39  {
40  
41      private static final Object[] KEYS;
42  
43      static
44      {
45          KEYS = new Object[16];
46          for ( int i = 0; i < KEYS.length; i++ )
47          {
48              KEYS[i] = new Object();
49          }
50      }
51  
52      private final String key;
53  
54      private final char[] value;
55  
56      private final int secretHash;
57  
58      SecretAuthentication( String key, String value )
59      {
60          this( ( value != null ) ? value.toCharArray() : null, key );
61      }
62  
63      SecretAuthentication( String key, char[] value )
64      {
65          this( copy( value ), key );
66      }
67  
68      private SecretAuthentication( char[] value, String key )
69      {
70          this.key = requireNonNull( key, "authentication key cannot be null" );
71          if ( key.length() == 0 )
72          {
73              throw new IllegalArgumentException( "authentication key cannot be empty" );
74          }
75          this.secretHash = Arrays.hashCode( value ) ^ KEYS[0].hashCode();
76          this.value = xor( value );
77      }
78  
79      private static char[] copy( char[] chars )
80      {
81          return ( chars != null ) ? chars.clone() : null;
82      }
83  
84      @SuppressWarnings( "checkstyle:magicnumber" )
85      private char[] xor( char[] chars )
86      {
87          if ( chars != null )
88          {
89              int mask = System.identityHashCode( this );
90              for ( int i = 0; i < chars.length; i++ )
91              {
92                  int key = KEYS[( i >> 1 ) % KEYS.length].hashCode();
93                  key ^= mask;
94                  chars[i] ^= ( ( i & 1 ) == 0 ) ? ( key & 0xFFFF ) : ( key >>> 16 );
95              }
96          }
97          return chars;
98      }
99  
100     private static void clear( char[] chars )
101     {
102         if ( chars != null )
103         {
104             for ( int i = 0; i < chars.length; i++ )
105             {
106                 chars[i] = '\0';
107             }
108         }
109     }
110 
111     public void fill( AuthenticationContext context, String key, Map<String, String> data )
112     {
113         requireNonNull( context, "context cannot be null" );
114         char[] secret = copy( value );
115         xor( secret );
116         context.put( this.key, secret );
117         // secret will be cleared upon AuthenticationContext.close()
118     }
119 
120     public void digest( AuthenticationDigest digest )
121     {
122         char[] secret = copy( value );
123         try
124         {
125             xor( secret );
126             digest.update( key );
127             digest.update( secret );
128         }
129         finally
130         {
131             clear( secret );
132         }
133     }
134 
135     @Override
136     public boolean equals( Object obj )
137     {
138         if ( this == obj )
139         {
140             return true;
141         }
142         if ( obj == null || !getClass().equals( obj.getClass() ) )
143         {
144             return false;
145         }
146         SecretAuthentication that = (SecretAuthentication) obj;
147         if ( !Objects.equals( key, that.key ) || secretHash != that.secretHash )
148         {
149             return false;
150         }
151         char[] secret = copy( value );
152         char[] thatSecret = copy( that.value );
153         try
154         {
155             xor( secret );
156             that.xor( thatSecret );
157             return Arrays.equals( secret, thatSecret );
158         }
159         finally
160         {
161             clear( secret );
162             clear( thatSecret );
163         }
164     }
165 
166     @Override
167     public int hashCode()
168     {
169         int hash = 17;
170         hash = hash * 31 + key.hashCode();
171         hash = hash * 31 + secretHash;
172         return hash;
173     }
174 
175     @Override
176     public String toString()
177     {
178         return key + "=" + ( ( value != null ) ? "***" : "null" );
179     }
180 
181 }