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; |
21 | |
22 | import java.io.IOException; |
23 | import java.io.InputStream; |
24 | import java.security.InvalidKeyException; |
25 | import java.security.KeyFactory; |
26 | import java.security.NoSuchAlgorithmException; |
27 | import java.security.PrivateKey; |
28 | import java.security.Signature; |
29 | import java.security.SignatureException; |
30 | import java.security.spec.InvalidKeySpecException; |
31 | import java.security.spec.PKCS8EncodedKeySpec; |
32 | import java.util.List; |
33 | |
34 | import org.apache.commons.codec.binary.Base64; |
35 | import org.apache.james.jdkim.api.BodyHasher; |
36 | import org.apache.james.jdkim.api.Headers; |
37 | import org.apache.james.jdkim.api.SignatureRecord; |
38 | import org.apache.james.jdkim.exceptions.FailException; |
39 | import org.apache.james.jdkim.exceptions.PermFailException; |
40 | import org.apache.james.jdkim.impl.BodyHasherImpl; |
41 | import org.apache.james.jdkim.impl.Message; |
42 | import org.apache.james.jdkim.tagvalue.SignatureRecordImpl; |
43 | import org.apache.james.mime4j.MimeException; |
44 | |
45 | public class DKIMSigner extends DKIMCommon { |
46 | |
47 | private PrivateKey privateKey; |
48 | private String signatureRecordTemplate; |
49 | |
50 | public DKIMSigner(String signatureRecordTemplate, PrivateKey privateKey) { |
51 | this.privateKey = privateKey; |
52 | this.signatureRecordTemplate = signatureRecordTemplate; |
53 | } |
54 | |
55 | public SignatureRecord newSignatureRecordTemplate(String record) { |
56 | return new SignatureRecordImpl(record); |
57 | } |
58 | |
59 | public BodyHasher newBodyHasher(SignatureRecord signRecord) |
60 | throws PermFailException { |
61 | return new BodyHasherImpl(signRecord); |
62 | } |
63 | |
64 | public String sign(InputStream is) throws IOException, FailException { |
65 | Message message; |
66 | try { |
67 | try { |
68 | message = new Message(is); |
69 | } catch (MimeException e1) { |
70 | throw new PermFailException("MIME parsing exception: " |
71 | + e1.getMessage(), e1); |
72 | } |
73 | SignatureRecord srt = newSignatureRecordTemplate(signatureRecordTemplate); |
74 | |
75 | BodyHasher bhj = newBodyHasher(srt); |
76 | |
77 | // computation of the body hash. |
78 | DKIMCommon.streamCopy(message.getBodyInputStream(), bhj |
79 | .getOutputStream()); |
80 | |
81 | return sign(message, bhj); |
82 | |
83 | } finally { |
84 | is.close(); |
85 | } |
86 | } |
87 | |
88 | public String sign(Headers message, BodyHasher bhj) |
89 | throws PermFailException { |
90 | byte[] computedHash = bhj.getDigest(); |
91 | |
92 | bhj.getSignatureRecord().setBodyHash(computedHash); |
93 | |
94 | List headers = bhj.getSignatureRecord().getHeaders(); |
95 | try { |
96 | // TODO handle b= in SignatureRecord. |
97 | // whenever any tag is changed the b should be invalidated and the |
98 | // text representation lost. |
99 | // whenever the b value is regenerated it should also be associated |
100 | // with the right test representation. |
101 | // we need a method to "regenerate the text representation" and to |
102 | // retrieve it when it is valid. |
103 | byte[] signatureHash = signatureSign(message, bhj |
104 | .getSignatureRecord(), privateKey, headers); |
105 | |
106 | bhj.getSignatureRecord().setSignature(signatureHash); |
107 | |
108 | return "DKIM-Signature:"+bhj.getSignatureRecord().toString(); |
109 | } catch (InvalidKeyException e) { |
110 | throw new PermFailException("Invalid key: " + e.getMessage(), e); |
111 | } catch (NoSuchAlgorithmException e) { |
112 | throw new PermFailException("Unknown algorythm: " + e.getMessage(), |
113 | e); |
114 | } catch (SignatureException e) { |
115 | throw new PermFailException("Signing exception: " + e.getMessage(), |
116 | e); |
117 | } |
118 | } |
119 | |
120 | private byte[] signatureSign(Headers h, SignatureRecord sign, PrivateKey key, List headers) |
121 | throws NoSuchAlgorithmException, InvalidKeyException, |
122 | SignatureException, PermFailException { |
123 | |
124 | Signature signature = Signature.getInstance(sign.getHashMethod() |
125 | .toString().toUpperCase() |
126 | + "with" + sign.getHashKeyType().toString().toUpperCase()); |
127 | signature.initSign(key); |
128 | |
129 | signatureCheck(h, sign, headers, signature); |
130 | return signature.sign(); |
131 | } |
132 | |
133 | /** |
134 | * Generate a PrivateKey from a Base64 encoded private key. |
135 | * |
136 | * In order to generate a valid PKCS8 key when you have a PEM key you can do |
137 | * this: <code> |
138 | * openssl pkcs8 -topk8 -inform PEM -in rsapriv.pem -outform DER -nocrypt -out rsapriv.der |
139 | * </code> |
140 | * And then base64 encode the content. |
141 | * |
142 | * @param privateKeyPKCS8 |
143 | * a Base64 encoded string of the RSA key in PKCS8 format |
144 | * @return the PrivateKey |
145 | * @throws NoSuchAlgorithmException |
146 | * if RSA is unknown |
147 | * @throws InvalidKeySpecException |
148 | * on bad input key |
149 | */ |
150 | public static PrivateKey getPrivateKey(String privateKeyPKCS8) |
151 | throws NoSuchAlgorithmException, InvalidKeySpecException { |
152 | byte[] encKey = Base64.decodeBase64(privateKeyPKCS8.getBytes()); |
153 | // byte[] encKey = privateKey.getBytes(); |
154 | PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(encKey); |
155 | KeyFactory keyFactory; |
156 | keyFactory = KeyFactory.getInstance("RSA"); |
157 | PrivateKey privKey = keyFactory.generatePrivate(privSpec); |
158 | return privKey; |
159 | } |
160 | |
161 | } |