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  
20  package org.apache.james.jdkim.tagvalue;
21  
22  import java.security.KeyFactory;
23  import java.security.NoSuchAlgorithmException;
24  import java.security.PublicKey;
25  import java.security.interfaces.RSAPublicKey;
26  import java.security.spec.InvalidKeySpecException;
27  import java.security.spec.X509EncodedKeySpec;
28  import java.util.ArrayList;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.regex.Pattern;
33  
34  import org.apache.commons.codec.binary.Base64;
35  import org.apache.james.jdkim.api.PublicKeyRecord;
36  
37  public class PublicKeyRecordImpl extends TagValue implements PublicKeyRecord {
38  
39      private static final String atom = "[a-zA-Z0-9!#$%&'*+/=?^_`{}|~-]+";
40      // TODO this should support CFWS: are they supported in DKIM for real?
41      private static final String dotAtomText = "(" + atom + ")?\\*?(" + atom
42              + ")?";
43      private static final Pattern granularityPattern = Pattern.compile("^"
44              + dotAtomText + "$");
45  
46      // SPEC: hyphenated-word = ALPHA [ *(ALPHA / DIGIT / "-") (ALPHA / DIGIT) ]
47      private static Pattern hyphenatedWordPattern = Pattern
48              .compile("^[a-zA-Z]([a-zA-Z0-9-]*[a-zA-Z0-9])?$");
49  
50      public PublicKeyRecordImpl(String data) {
51          super(data);
52      }
53  
54      protected Map newTagValue() {
55          // extensions may override this to use TreeMaps in order to keep track
56          // of orders
57          return new LinkedHashMap();
58      }
59  
60      protected void init() {
61          mandatoryTags.add("p");
62          defaults.put("v", "DKIM1");
63          defaults.put("g", "*");
64          defaults.put("h", ANY);
65          defaults.put("k", "rsa");
66          defaults.put("s", "*");
67          defaults.put("t", "");
68      }
69  
70      // TODO do we treat v=NONDKIM1 records, syntax error records and v=DKIM1 in
71      // the middle records in the same way?
72      public void validate() {
73          super.validate();
74          if (containsTag("v")) {
75              // if "v" is specified it must be the first tag
76              String firstKey = (String) tagSet().iterator().next();
77              if (!"v".equals(firstKey))
78                  throw new IllegalStateException(
79                          "Existing v= tag MUST be the first in the record list ("
80                                  + firstKey + ")");
81          }
82          if (!"DKIM1".equals(getValue("v")))
83              throw new IllegalStateException(
84                      "Unknown version for v= (expected DKIM1): " + getValue("v"));
85          if ("".equals(getValue("p")))
86              throw new IllegalStateException("Revoked key. 'p=' in record");
87      }
88  
89      /**
90       * @see org.apache.james.jdkim.api.PublicKeyRecord#isHashMethodSupported(java.lang.CharSequence)
91       */
92      public boolean isHashMethodSupported(CharSequence hash) {
93          List hashes = getAcceptableHashMethods();
94          if (hashes == null)
95              return true;
96          return isInListCaseInsensitive(hash, hashes);
97      }
98  
99      /**
100      * @see org.apache.james.jdkim.api.PublicKeyRecord#isKeyTypeSupported(java.lang.CharSequence)
101      */
102     public boolean isKeyTypeSupported(CharSequence hash) {
103         List hashes = getAcceptableKeyTypes();
104         return isInListCaseInsensitive(hash, hashes);
105     }
106 
107     /**
108      * @see org.apache.james.jdkim.api.PublicKeyRecord#getAcceptableHashMethods()
109      */
110     public List/* String */getAcceptableHashMethods() {
111         if (ANY.equals(getValue("h")))
112             return null;
113         return stringToColonSeparatedList(getValue("h").toString(),
114                 hyphenatedWordPattern);
115     }
116 
117     /**
118      * @see org.apache.james.jdkim.api.PublicKeyRecord#getAcceptableKeyTypes()
119      */
120     public List/* String */getAcceptableKeyTypes() {
121         return stringToColonSeparatedList(getValue("k").toString(),
122                 hyphenatedWordPattern);
123     }
124 
125     /**
126      * @see org.apache.james.jdkim.api.PublicKeyRecord#getGranularityPattern()
127      */
128     public Pattern getGranularityPattern() {
129         String g = getValue("g").toString();
130         int pStar = g.indexOf('*');
131         if (VALIDATION) {
132             if (!granularityPattern.matcher(g).matches())
133                 throw new IllegalStateException("Syntax error in granularity: "
134                         + g);
135         }
136         if (g.length() == 0) {
137             // TODO this works but smells too much as an hack.
138             // in case of "g=" with nothing specified then we return a pattern
139             // that won't match
140             // SPEC: An empty "g=" value never matches any addresses.
141             return Pattern.compile("@");
142         } else if (pStar != -1) {
143             if (g.indexOf('*', pStar + 1) != -1)
144                 throw new IllegalStateException(
145                         "Invalid granularity using more than one wildcard: "
146                                 + g);
147             String pattern = "^\\Q" + g.subSequence(0, pStar).toString()
148                     + "\\E.*\\Q"
149                     + g.subSequence(pStar + 1, g.length()).toString() + "\\E$";
150             return Pattern.compile(pattern);
151         } else {
152             // TODO we need some escaping. On Java 5 we have Pattern.quote that
153             // is better
154             return Pattern.compile("^\\Q" + g + "\\E$");
155         }
156     }
157 
158     public List getFlags() {
159         String flags = getValue("t").toString();
160         String[] flagsStrings = flags.split(":");
161         List res = new ArrayList();
162         for (int i = 0; i < flagsStrings.length; i++) {
163             res.add(trimFWS(flagsStrings[i], 0, flagsStrings[i].length() - 1,
164                     true).toString());
165         }
166         return res;
167     }
168 
169     public boolean isDenySubdomains() {
170         return getFlags().contains("s");
171     }
172 
173     public boolean isTesting() {
174         return getFlags().contains("y");
175     }
176 
177     /**
178      * @see org.apache.james.jdkim.api.PublicKeyRecord#getPublicKey()
179      */
180     public PublicKey getPublicKey() {
181         try {
182             String p = getValue("p").toString();
183             byte[] key = Base64.decodeBase64(p.getBytes());
184             KeyFactory keyFactory;
185             keyFactory = KeyFactory.getInstance(getValue("k").toString());
186             X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(key);
187             RSAPublicKey rsaKey;
188             rsaKey = (RSAPublicKey) keyFactory.generatePublic(pubSpec);
189             return rsaKey;
190         } catch (NoSuchAlgorithmException e) {
191             throw new IllegalStateException("Unknown algorithm: "
192                     + e.getMessage());
193         } catch (InvalidKeySpecException e) {
194             throw new IllegalStateException("Invalid key spec: "
195                     + e.getMessage());
196         }
197     }
198 
199 }