13bf1d5c622f317e0d2cd572dd8bb050a53fb9e9
[iotivity.git] / cloud / account / src / main / java / org / iotivity / cloud / accountserver / resources / credprov / cert / CertificateResource.java
1 /*
2  * //******************************************************************
3  * //
4  * // Copyright 2016 Samsung Electronics All Rights Reserved.
5  * //
6  * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
7  * //
8  * // Licensed under the Apache License, Version 2.0 (the "License");
9  * // you may not use this file except in compliance with the License.
10  * // 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, software
15  * // distributed under the License is distributed on an "AS IS" BASIS,
16  * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * // See the License for the specific language governing permissions and
18  * // limitations under the License.
19  * //
20  * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
21  */
22 package org.iotivity.cloud.accountserver.resources.credprov.cert;
23
24 import org.bouncycastle.asn1.ASN1Encodable;
25 import org.bouncycastle.asn1.DERSequence;
26 import org.bouncycastle.asn1.x509.Extension;
27 import org.bouncycastle.asn1.x509.GeneralName;
28 import org.bouncycastle.cert.CertIOException;
29 import org.bouncycastle.jce.provider.BouncyCastleProvider;
30 import org.bouncycastle.operator.OperatorCreationException;
31 import org.bouncycastle.util.encoders.Base64;
32 import org.iotivity.cloud.accountserver.Constants;
33 import org.iotivity.cloud.accountserver.db.CertificateTable;
34 import org.iotivity.cloud.accountserver.resources.credprov.crl.CrlManager;
35 import org.iotivity.cloud.accountserver.x509.cert.CSRParser;
36 import org.iotivity.cloud.accountserver.x509.cert.CertificateBuilder;
37 import org.iotivity.cloud.accountserver.x509.cert.CertificateExtension;
38 import org.iotivity.cloud.accountserver.x509.cert.Utility;
39 import org.iotivity.cloud.base.device.Device;
40 import org.iotivity.cloud.base.exception.ServerException;
41 import org.iotivity.cloud.base.protocols.IRequest;
42 import org.iotivity.cloud.base.protocols.IResponse;
43 import org.iotivity.cloud.base.protocols.MessageBuilder;
44 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
45 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
46 import org.iotivity.cloud.base.resource.Resource;
47 import org.iotivity.cloud.util.Cbor;
48 import org.iotivity.cloud.util.Log;
49
50 import java.io.IOException;
51 import java.security.GeneralSecurityException;
52 import java.security.PublicKey;
53 import java.security.Security;
54 import java.security.cert.CRLException;
55 import java.security.cert.X509Certificate;
56 import java.util.Arrays;
57 import java.util.HashMap;
58 import java.util.Map;
59 import java.util.regex.Matcher;
60 import java.util.regex.Pattern;
61
62 import static org.iotivity.cloud.accountserver.Constants.*;
63 import static org.iotivity.cloud.accountserver.resources.credprov.cert.CertificateConstants.BASE_64;
64 import static org.iotivity.cloud.accountserver.resources.credprov.cert.CertificateConstants.KEYSTORE_FILE;
65
66 /**
67  * This class provides access for certificate resource.
68  * Devices can send CSR requests in CBOR format to this resource
69  * and get responses in the same format. Response contains device identifier,
70  * personal certificate, issued by CA certificate and certificate chain.
71  */
72 public class CertificateResource extends Resource {
73
74     /**
75      * This constant object is used for parsing cbor payload to Map object and to
76      * encoding map object to cbor format.
77      */
78     private static final Cbor<Map<String, Object>> MAP_CBOR = new Cbor<>();
79
80     /**
81      * Inserts BouncyCastleProvider into 0 position in security provider list,
82      * inits KeyStore, generates CA certificate and saves it to keyStore.
83      */
84     static {
85         Security.insertProviderAt(new BouncyCastleProvider(), 0);
86         try {
87             if (!KEYSTORE_FILE.exists()) {
88                 CertificateStorage.init();
89             } else {
90                 CertificateStorage.load();
91             }
92         } catch (GeneralSecurityException | IOException | OperatorCreationException e) {
93             Log.e(e.getMessage());
94         }
95     }
96
97     /**
98      * Constructs certificate resource with specified prefixes.
99      */
100     public CertificateResource() {
101         super(Arrays.asList(PREFIX_OIC, CREDPROV_URI, CERT_URI));
102     }
103
104     @Override
105     public void onDefaultRequestReceived(Device srcDevice, IRequest request)
106             throws ServerException {
107         IResponse response;
108         switch (request.getMethod()) {
109             case POST:
110                 response = handlePostRequest(request);
111                 break;
112             default:
113                 response = MessageBuilder.createResponse(request, ResponseStatus.METHOD_NOT_ALLOWED);
114         }
115         srcDevice.sendResponse(response);
116     }
117
118     /**
119      * Handles post requests to Certificate Resource.
120      * Request should be with specified format
121      * POST /oic/credprov/cert
122      * {
123      *      “di” : “11-22-xx”,
124      *      “csr” : {
125      *          “encoding” : “oic.sec.encoding.base64”,
126      *          “data” : “<Base64 encoded CSR Binary>”
127      *      }
128      * }
129      * Method checks encoding, and decodes data by specified encoding if needed.
130      *
131      * Method issus a certificate including User UUID in extension field,
132      * stores issuing information (serial number, validity, device uuid, user uuid) for management (e.g. re-issue).
133      * Response should be in next format for example:
134      * 2.04 CHANGED
135      * {
136      *      “di” : “1111-22-xx”,
137      *      “cert” : {
138      *          “encoding” : “oic.sec.encoding.base64”,
139      *          “data” : “<Base64 encoded Cert. Binary>”
140      *       },
141      *      “certchain” : {
142      *          “encoding” : “oic.sec.encoding.base64”,
143      *          “data” : “<Base64 encoded CA Cert. chain>”
144      *       }
145      * }
146      * or returns BAD_REQUEST: 4.0.1 if any exceptions occured.
147      *
148      * @param request request with payload information.
149      * @throws ServerException
150      */
151     private IResponse handlePostRequest(IRequest request)
152             throws ServerException {
153         byte[] requestPayload = request.getPayload();
154         IResponse response = MessageBuilder.createResponse(request, ResponseStatus.BAD_REQUEST);
155         if (requestPayload != null) {
156             Map<String, Object> payloadData = MAP_CBOR
157                     .parsePayloadFromCbor(requestPayload, HashMap.class);
158             if (payloadData != null) {
159                 Object csr = payloadData.get(Constants.REQ_CSR);
160                 if (csr != null && csr instanceof Map) {
161                     Object encoding = ((Map<String, Object>) csr).get(ENCODING);
162                     Object data = ((Map<String, Object>) csr).get(DATA);
163                     if (encoding != null && encoding instanceof String && data != null && data instanceof byte[]) {
164                         byte[] csrData = (byte[]) data;
165                         if (encoding.equals(BASE_64)) {
166                             csrData = Base64.decode(csrData);
167                         }
168                         try {
169                             CSRParser parser = new CSRParser(csrData);
170                             String commonName = parser.getCommonName();
171                             String pattern = "^uuid:(.*)$";
172                             Pattern r = Pattern.compile(pattern);
173                             Matcher m = r.matcher(commonName);
174                             String deviceId = (String) payloadData.get(RESP_DEVICE_ID);
175                             if (m.find() && m.group(1).equals(deviceId) && parser.isSignatureValid()) {
176                                 CertificateManager certificateManager = new CertificateManager(deviceId);
177                                 CertificateTable certificateTable = certificateManager.getCertificate();
178                                 if (certificateTable != null) {
179                                     try {
180                                         CrlManager.CRL_MANAGER.revoke(certificateTable.getSerialNumber());
181                                     } catch (CRLException | OperatorCreationException e) {
182                                         Log.e(e.getMessage() + e.getClass());
183                                     }
184                                     certificateManager.update(certificateTable, true);
185                                 }
186                                 PublicKey publicKey = parser.getPublicKey();
187                                 if (publicKey != null) {
188                                     CertificateExtension extension = new CertificateExtension(Extension.subjectAlternativeName,
189                                             false, new DERSequence(new ASN1Encodable[]
190                                             {new GeneralName(GeneralName.dNSName, Constants.KEYFIELD_USERID + ":" +
191                                                     Utility.getUserID(deviceId))}));
192                                     CertificateBuilder certBuilder = new CertificateBuilder(parser.getSubject(),
193                                             publicKey, extension);
194                                     try {
195                                         X509Certificate personal = certBuilder.build();
196                                         byte[] encodedCert = personal.getEncoded();
197                                         byte[] encodedCa = CertificateStorage.ROOT_CERTIFICATE.getEncoded();
198                                         if (encoding.equals(CertificateConstants.BASE_64)) {
199                                             encodedCert = Base64.encode(encodedCert);
200                                             encodedCa = Base64.encode(encodedCa);
201                                         }
202                                         certificateManager.put(Constants.RESP_DEVICE_ID, deviceId);
203                                         certificateManager.put(Constants.CERT, new CSR(encoding.toString(), encodedCert));
204                                         certificateManager.put(Constants.CERT_CHAIN, new CSR(encoding.toString(), encodedCa));
205                                         certificateManager.save(personal.getSerialNumber(), personal.getNotAfter(),
206                                                 personal.getNotBefore());
207                                         response = MessageBuilder.createResponse(request, ResponseStatus.CHANGED,
208                                                 ContentFormat.APPLICATION_CBOR,
209                                                 MAP_CBOR.encodingPayloadToCbor(certificateManager.getPayLoad()));
210                                     } catch (GeneralSecurityException | OperatorCreationException | CertIOException e) {
211                                         Log.e(e.getMessage());
212                                     }
213                                 }
214                             }
215                         } catch (IOException e) {
216                             Log.e(e.getMessage());
217                         }
218                     }
219                 }
220             }
221         }
222         return response;
223     }
224
225     /**
226      * Response utility class.
227      */
228     private static final class CSR {
229
230         private final String encoding;
231
232         private final byte[] data;
233
234         CSR(String encoding, byte[] data) {
235             this.encoding = encoding;
236             this.data = data;
237         }
238
239         /**
240          * Return encoding.
241          */
242         public String getEncoding() {
243             return encoding;
244         }
245
246         /**
247          * Retrieves data.
248          */
249         public byte[] getData() {
250             return data;
251         }
252     }
253 }