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      @SuppressWarnings("checkstyle:magicnumber")
75      private char[] xor(char[] chars) {
76          if (chars != null) {
77              int mask = System.identityHashCode(this);
78              for (int i = 0; i < chars.length; i++) {
79                  int key = KEYS[(i >> 1) % KEYS.length].hashCode();
80                  key ^= mask;
81                  chars[i] ^= ((i & 1) == 0) ? (key & 0xFFFF) : (key >>> 16);
82              }
83          }
84          return chars;
85      }
86  
87      private static void clear(char[] chars) {
88          if (chars != null) {
89              for (int i = 0; i < chars.length; i++) {
90                  chars[i] = '\0';
91              }
92          }
93      }
94  
95      public void fill(AuthenticationContext context, String key, Map<String, String> data) {
96          requireNonNull(context, "context cannot be null");
97          char[] secret = copy(value);
98          xor(secret);
99          context.put(this.key, secret);
100         // secret will be cleared upon AuthenticationContext.close()
101     }
102 
103     public void digest(AuthenticationDigest digest) {
104         char[] secret = copy(value);
105         try {
106             xor(secret);
107             digest.update(key);
108             digest.update(secret);
109         } finally {
110             clear(secret);
111         }
112     }
113 
114     @Override
115     public boolean equals(Object obj) {
116         if (this == obj) {
117             return true;
118         }
119         if (obj == null || !getClass().equals(obj.getClass())) {
120             return false;
121         }
122         SecretAuthentication that = (SecretAuthentication) obj;
123         if (!Objects.equals(key, that.key) || secretHash != that.secretHash) {
124             return false;
125         }
126         char[] secret = copy(value);
127         char[] thatSecret = copy(that.value);
128         try {
129             xor(secret);
130             that.xor(thatSecret);
131             return Arrays.equals(secret, thatSecret);
132         } finally {
133             clear(secret);
134             clear(thatSecret);
135         }
136     }
137 
138     @Override
139     public int hashCode() {
140         int hash = 17;
141         hash = hash * 31 + key.hashCode();
142         hash = hash * 31 + secretHash;
143         return hash;
144     }
145 
146     @Override
147     public String toString() {
148         return key + "=" + ((value != null) ? "***" : "null");
149     }
150 }