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