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         char[] secret = copy( value );
114         xor( secret );
115         context.put( this.key, secret );
116         // secret will be cleared upon AuthenticationContext.close()
117     }
118 
119     public void digest( AuthenticationDigest digest )
120     {
121         char[] secret = copy( value );
122         try
123         {
124             xor( secret );
125             digest.update( key );
126             digest.update( secret );
127         }
128         finally
129         {
130             clear( secret );
131         }
132     }
133 
134     @Override
135     public boolean equals( Object obj )
136     {
137         if ( this == obj )
138         {
139             return true;
140         }
141         if ( obj == null || !getClass().equals( obj.getClass() ) )
142         {
143             return false;
144         }
145         SecretAuthentication that = (SecretAuthentication) obj;
146         if ( !Objects.equals( key, that.key ) || secretHash != that.secretHash )
147         {
148             return false;
149         }
150         char[] secret = copy( value );
151         char[] thatSecret = copy( that.value );
152         try
153         {
154             xor( secret );
155             that.xor( thatSecret );
156             return Arrays.equals( secret, thatSecret );
157         }
158         finally
159         {
160             clear( secret );
161             clear( thatSecret );
162         }
163     }
164 
165     @Override
166     public int hashCode()
167     {
168         int hash = 17;
169         hash = hash * 31 + key.hashCode();
170         hash = hash * 31 + secretHash;
171         return hash;
172     }
173 
174     @Override
175     public String toString()
176     {
177         return key + "=" + ( ( value != null ) ? "***" : "null" );
178     }
179 
180 }