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 static java.util.Objects.requireNonNull;
25  
26  import org.eclipse.aether.repository.Authentication;
27  import org.eclipse.aether.repository.AuthenticationContext;
28  import org.eclipse.aether.repository.AuthenticationDigest;
29  
30  /**
31   * Authentication block that manages a single authentication key and its secret string value (password, passphrase).
32   * Unlike {@link StringAuthentication}, the string value is kept in an encrypted buffer and only decrypted when needed
33   * to reduce the potential of leaking the secret in a heap dump.
34   */
35  final class SecretAuthentication
36      implements Authentication
37  {
38  
39      private static final Object[] KEYS;
40  
41      static
42      {
43          KEYS = new Object[16];
44          for ( int i = 0; i < KEYS.length; i++ )
45          {
46              KEYS[i] = new Object();
47          }
48      }
49  
50      private final String key;
51  
52      private final char[] value;
53  
54      private final int secretHash;
55  
56      public SecretAuthentication( String key, String value )
57      {
58          this( ( value != null ) ? value.toCharArray() : null, key );
59      }
60  
61      public SecretAuthentication( String key, char[] value )
62      {
63          this( copy( value ), key );
64      }
65  
66      private SecretAuthentication( char[] value, String key )
67      {
68          this.key = requireNonNull( key, "authentication key cannot be null" );
69          if ( key.length() == 0 )
70          {
71              throw new IllegalArgumentException( "authentication key cannot be empty" );
72          }
73          this.secretHash = Arrays.hashCode( value ) ^ KEYS[0].hashCode();
74          this.value = xor( value );
75      }
76  
77      private static char[] copy( char[] chars )
78      {
79          return ( chars != null ) ? chars.clone() : null;
80      }
81  
82      private char[] xor( char[] chars )
83      {
84          if ( chars != null )
85          {
86              int mask = System.identityHashCode( this );
87              for ( int i = 0; i < chars.length; i++ )
88              {
89                  int key = KEYS[( i >> 1 ) % KEYS.length].hashCode();
90                  key ^= mask;
91                  chars[i] ^= ( ( i & 1 ) == 0 ) ? ( key & 0xFFFF ) : ( key >>> 16 );
92              }
93          }
94          return chars;
95      }
96  
97      private static void clear( char[] chars )
98      {
99          if ( chars != null )
100         {
101             for ( int i = 0; i < chars.length; i++ )
102             {
103                 chars[i] = '\0';
104             }
105         }
106     }
107 
108     public void fill( AuthenticationContext context, String key, Map<String, String> data )
109     {
110         char[] secret = copy( value );
111         xor( secret );
112         context.put( this.key, secret );
113         // secret will be cleared upon AuthenticationContext.close()
114     }
115 
116     public void digest( AuthenticationDigest digest )
117     {
118         char[] secret = copy( value );
119         try
120         {
121             xor( secret );
122             digest.update( key );
123             digest.update( secret );
124         }
125         finally
126         {
127             clear( secret );
128         }
129     }
130 
131     @Override
132     public boolean equals( Object obj )
133     {
134         if ( this == obj )
135         {
136             return true;
137         }
138         if ( obj == null || !getClass().equals( obj.getClass() ) )
139         {
140             return false;
141         }
142         SecretAuthentication that = (SecretAuthentication) obj;
143         if ( !eq( key, that.key ) || secretHash != that.secretHash )
144         {
145             return false;
146         }
147         char[] secret = copy( value );
148         char[] thatSecret = copy( that.value );
149         try
150         {
151             xor( secret );
152             that.xor( thatSecret );
153             return Arrays.equals( secret, thatSecret );
154         }
155         finally
156         {
157             clear( secret );
158             clear( thatSecret );
159         }
160     }
161 
162     private static <T> boolean eq( T s1, T s2 )
163     {
164         return s1 != null ? s1.equals( s2 ) : s2 == null;
165     }
166 
167     @Override
168     public int hashCode()
169     {
170         int hash = 17;
171         hash = hash * 31 + key.hashCode();
172         hash = hash * 31 + secretHash;
173         return hash;
174     }
175 
176     @Override
177     public String toString()
178     {
179         return key + "=" + ( ( value != null ) ? "***" : "null" );
180     }
181 
182 }