1 /*
2 * The MIT License
3 *
4 * Original work sponsored and donated by National Board of e-Health (NSI), Denmark (http://www.nsi.dk)
5 *
6 * Copyright (C) 2011 National Board of e-Health (NSI), Denmark (http://www.nsi.dk)
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy of
9 * this software and associated documentation files (the "Software"), to deal in
10 * the Software without restriction, including without limitation the rights to
11 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 * of the Software, and to permit persons to whom the Software is furnished to do
13 * so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 *
26 * $HeadURL: https://svn.softwareborsen.dk/sosi/trunk/modules/seal/src/main/java/dk/sosi/seal/model/IDCard.java $
27 * $Id: IDCard.java 9703 2012-01-10 11:30:03Z chg@lakeside.dk $
28 */
29 package dk.sosi.seal.model;
30
31 import dk.sosi.seal.SOSIFactory;
32 import dk.sosi.seal.model.constants.DGWSConstants;
33 import dk.sosi.seal.model.constants.IDValues;
34 import dk.sosi.seal.model.dombuilders.IDCardDOMBuilder;
35 import dk.sosi.seal.pki.CredentialVaultSignatureProvider;
36 import dk.sosi.seal.pki.SignatureProvider;
37 import dk.sosi.seal.vault.CredentialVault;
38 import dk.sosi.seal.xml.XmlUtil;
39 import org.w3c.dom.Document;
40 import org.w3c.dom.Element;
41
42 import java.io.Serializable;
43 import java.security.cert.X509Certificate;
44 import java.util.Date;
45
46 /**
47 * An abstract class representing a SOSI ID card. Please refer to concrete subclasses for further information.
48 * <p>
49 * Subclasses of this class <b>must</b> be immutable. If not, the DOM caching will break.
50 *
51 * @author Jan Riis
52 * @author $LastChangedBy: chg@lakeside.dk $
53 * @since 1.0
54 */
55 public abstract class IDCard implements Serializable {
56
57 /** ID Card type for system ID cards */
58 public static final String IDCARDTYPE_SYSTEM = "system";
59
60 /** ID Card type for user (personal) ID cards */
61 public static final String IDCARDTYPE_USER = "user";
62
63 /**
64 * Set the ID card starttime to "now" minus this amount of minutes, to circumvent minor differences in the
65 * server/client clocks.
66 */
67 private static final int IDCARD_BEGIN_TIME_BUFFER_IN_MINUTES = 5;
68
69 /**
70 * Maximum lifetime of an ID-Card
71 */
72 private static final int MAX_IDCARD_LIFE_IN_HOURS = 24;
73
74 private final String id;
75 private final Date createdDate;
76 private final Date expiryDate;
77 private final String issuer;
78 private final AuthenticationLevel authenticationLevel;
79 private String certHash;
80 private final String alternativeIdentifier;
81 private String username;
82 private String password;
83
84 /** An DOM element that is the current representation of this IDCard. */
85 // TODO: consider to make this element transient - BUT beware of dependencies to the "needsSignature" value and the
86 // invariant needsSignature=(domElement==null)
87 protected Element domElement = null;
88
89 /** Does the IDCard need a signature */
90 protected boolean needsSignature;
91
92 /**
93 * Indicates the last operation performed on the DOM element, i.e. CREATED, SIGNED etc.
94 */
95 protected String lastDOMOperation = null;
96
97 /** State to indicate that DOM element has been created */
98 protected static final String CREATED = "ObjectCreated";
99 /** State to indicate that DOM element has been re-assigned */
100 protected static final String RE_ASSIGNED = "NodeReAssignedToNewDocument";
101 /** State to indicate that DOM element has been signed */
102 protected static final String SIGNED = "SignatureCreated";
103
104 private final String version;
105
106 // ===========================================
107 // Constructors
108 // ===========================================
109
110 /**
111 * Constructs an <code>IDCard</code> instance.
112 *
113 * @param version
114 * The version of this IDCard, corresponds to DGWS version
115 * @param domElement
116 * An optional DOM element that is the current representation of this IDCard.
117 * @param cardID
118 * The unique ID for this IDCard instance.
119 * @param authenticationLevel
120 * Specifies the authentication level for this ID-card, or more precisely the strength of the credentials
121 * the user/system presented when this ID-card was issued. Please refer to the "Den Gode Web Service"
122 * specification for more details.
123 * @param certHash
124 * The hash code of the certificate that was used as credentials for the issuance this IDCard. May be
125 * <code>null</code>.
126 * @param creationDate
127 * The unmarshalled expirydate (and time) for this <code>IDCard</code>
128 * @param expiryDate
129 * The unmarshalled creationdate (and time) for this <code>IDCard</code>
130 * @param alternativeIdentifier
131 * The unmarshalled alternative identifier for this <code>IDCard</code> or <code>null</code>.
132 * @param username
133 * The username to employ when creating an idcard with authenticationLeve 2. May be <code>null</code>.
134 * @param password
135 * The password to employ when creating an idcard with authenticationLeve 2. May be <code>null</code>.
136 */
137 IDCard(String version, Element domElement, String cardID, AuthenticationLevel authenticationLevel, String certHash,
138 String issuer, Date creationDate, Date expiryDate, String alternativeIdentifier, String username,
139 String password) {
140
141 super();
142 if(!DGWSConstants.SUPPORTED_VERSIONS.contains(version))
143 throw new ModelException("IDCard version '" + version + "' not supported. Supported versions are: "
144 + DGWSConstants.SUPPORTED_VERSIONS);
145 ModelUtil.validateNotNull(cardID, "IDCard ID cannot be 'null'");
146 ModelUtil.validateNotEmpty(issuer, "'Issuer' cannot be null or empty");
147 ModelUtil.validateNotNull(authenticationLevel, "'AuthenticationLevel' cannot be null");
148
149 this.version = version;
150 this.createdDate = creationDate;
151 this.expiryDate = expiryDate;
152 this.issuer = issuer;
153 this.authenticationLevel = authenticationLevel;
154 if(AuthenticationLevel.MOCES_TRUSTED_USER.equals(authenticationLevel)
155 || AuthenticationLevel.VOCES_TRUSTED_SYSTEM.equals(authenticationLevel)) {
156 this.certHash = certHash;
157 }
158 this.domElement = domElement;
159 this.alternativeIdentifier = alternativeIdentifier;
160 if(AuthenticationLevel.USERNAME_PASSWORD_AUTHENTICATION.equals(authenticationLevel)) {
161 ModelUtil.validateNotEmpty(username, "'username' cannot be null or empty for authenticationlevel 2");
162 ModelUtil.validateNotEmpty(password, "'password' cannot be null or empty for authenticationlevel 2");
163 this.username = username;
164 this.password = password;
165 }
166
167 // This is an invariant! When the IDCard is created from deserialization,
168 // the ID card is already signed => needsSignature=false
169 needsSignature = (domElement == null);
170
171 id = cardID;
172 }
173
174 /**
175 * Creates a brand new ID-card.
176 *
177 * @param version
178 * The version of this IDCard, corresponds to DGWS version
179 * @param authenticationLevel
180 * The level of trust a system can have to this IDCard
181 * @param issuer
182 * A <code>String</code> representing the system that issues the ID-Card
183 * @param certHash
184 * A SHA-1 digest of the certificate that can validate this ID-card. May be <code>null</code>.
185 * @param alternativeIdentifier
186 * A <code>String</code> denoting an alternative identifier that will be used as SAML Subject (of type
187 * medcom:other) when serializing this IDCard instead. May be <code>null</null>.
188 * @param username
189 * The username to employ when creating an idcard with authenticationLeve 2. May be <code>null</code>.
190 * @param password
191 * The password to employ when creating an idcard with authenticationLeve 2. May be <code>null</code>.
192 */
193 IDCard(String version, AuthenticationLevel authenticationLevel, String issuer, String certHash,
194 String alternativeIdentifier, String username, String password) {
195 super();
196 if(!DGWSConstants.SUPPORTED_VERSIONS.contains(version))
197 throw new ModelException("IDCard version '" + version + "' not supported. Supported versions are: "
198 + DGWSConstants.SUPPORTED_VERSIONS);
199 ModelUtil.validateNotEmpty(issuer, "'Issuer' cannot be null or empty");
200 ModelUtil.validateNotNull(authenticationLevel, "'AuthenticationLevel' cannot be null");
201
202 this.version = version;
203 long starttime = System.currentTimeMillis() - IDCARD_BEGIN_TIME_BUFFER_IN_MINUTES * 60 * 1000;
204 this.createdDate = new Date(starttime);
205 this.expiryDate = new Date(starttime + MAX_IDCARD_LIFE_IN_HOURS * 60 * 60 * 1000);
206
207 this.issuer = issuer;
208 this.authenticationLevel = authenticationLevel;
209 if(AuthenticationLevel.MOCES_TRUSTED_USER.equals(authenticationLevel)
210 || AuthenticationLevel.VOCES_TRUSTED_SYSTEM.equals(authenticationLevel)) {
211 this.certHash = certHash;
212 }
213 this.alternativeIdentifier = alternativeIdentifier;
214 if(AuthenticationLevel.USERNAME_PASSWORD_AUTHENTICATION.equals(authenticationLevel)) {
215 ModelUtil.validateNotEmpty(username, "'username' cannot be null or empty for authenticationlevel 2");
216 ModelUtil.validateNotEmpty(password, "'password' cannot be null or empty for authenticationlevel 2");
217 this.username = username;
218 this.password = password;
219 }
220
221 // This is an invariant! When the IDCard is created from deserialization,
222 // the ID card is already signed => needsSignature=false
223 needsSignature = (domElement == null);
224
225 id = XmlUtil.createGUID();
226 }
227
228 /**
229 * Creates a brand new ID-card based on values from another <code>IDCard</code> instance. The constructed IDCard
230 * will get a new <code>id</code>, creation date/time, expiry date/time and will scheduled for VOCES signing, making
231 * this constructor ideal for IdP ID-card issuers.
232 *
233 * @param toCopy
234 * the <code>IDCard</code> to copy
235 * @param issuer
236 * A <code>String</code> representing the system that issues the ID-Card
237 */
238 IDCard(IDCard toCopy, String issuer) {
239 this(toCopy, issuer, toCopy.getCertHash());
240 }
241
242 /**
243 * Creates a brand new ID-card based on values from another <code>IDCard</code> instance. The constructed IDCard
244 * will get a new <code>id</code>, creation date/time, expiry date/time and will scheduled for VOCES signing, making
245 * this constructor ideal for STS ID-card issuers.
246 *
247 * @param toCopy
248 * the <code>IDCard</code> to copy
249 * @param issuer
250 * A <code>String</code> representing the system that issues the ID-Card
251 * @param certHash
252 * The hash code of the certificate that was used as credentials for the issuance this IDCard. May be
253 * <code>null</code>.
254 */
255 IDCard(IDCard toCopy, String issuer, String certHash) {
256
257 super();
258 if(issuer == null)
259 throw new ModelException("'Issuer' cannot be null");
260
261 this.version = toCopy.getVersion();
262 long starttime = System.currentTimeMillis() - IDCARD_BEGIN_TIME_BUFFER_IN_MINUTES * 60 * 1000;
263 this.createdDate = new Date(starttime);
264 this.expiryDate = new Date(starttime + MAX_IDCARD_LIFE_IN_HOURS * 60 * 60 * 1000);
265 this.issuer = issuer;
266 this.authenticationLevel = toCopy.getAuthenticationLevel();
267 if(AuthenticationLevel.MOCES_TRUSTED_USER.equals(authenticationLevel)
268 || AuthenticationLevel.VOCES_TRUSTED_SYSTEM.equals(authenticationLevel)) {
269 this.certHash = certHash;
270 }
271 this.alternativeIdentifier = toCopy.getAlternativeIdentifier();
272 if(AuthenticationLevel.USERNAME_PASSWORD_AUTHENTICATION.equals(authenticationLevel)) {
273 this.username = toCopy.getUsername();
274 this.password = toCopy.getPassword();
275 }
276
277 // This is an invariant! When the IDCard is created from deserialization,
278 // the ID card is already signed => needsSignature=false
279 needsSignature = (domElement == null);
280 id = XmlUtil.createGUID();
281 }
282
283 // ===========================================
284 // Public methods
285 // ===========================================
286
287 /**
288 * Validates the validity of the IDCard
289 */
290 public boolean isValidInTime() {
291 Date now = new Date();
292 return now.equals(getCreatedDate()) || now.after(getCreatedDate()) && now.before(getExpiryDate());
293 }
294
295 /**
296 * Returns the version of this ID-Card
297 */
298 public String getVersion() {
299
300 return version;
301 }
302
303 /**
304 * Returns the ID of this ID-Card (unique in the federation).
305 */
306 public String getIDCardID() {
307
308 return id;
309 }
310
311 /**
312 * Returns the createdDate.
313 */
314 public Date getCreatedDate() {
315
316 return createdDate;
317 }
318
319 /**
320 * Returns the expiryDate.
321 */
322 public Date getExpiryDate() {
323
324 return expiryDate;
325 }
326
327 /**
328 * Returns the issuer.
329 */
330 public String getIssuer() {
331
332 return issuer;
333 }
334
335 /**
336 * Returns the authentication authenticationLevel for this IDCard. In essence this attribute tells how strong
337 * identification credentials the user (or system) presented to the IDCard issuer. The stronger the credentials, the
338 * higher authentication authenticationLevel.
339 */
340 public AuthenticationLevel getAuthenticationLevel() {
341
342 return authenticationLevel;
343 }
344
345 /**
346 * Returns the alternative identifier for this <code>IDCard</code> or <code>null</code> if no such identifier has
347 * been specified.
348 */
349 public String getAlternativeIdentifier() {
350 return alternativeIdentifier;
351 }
352
353 /**
354 * Returns a SHA-1 digest of the certificate that can validate this ID-card.
355 */
356 public String getCertHash() {
357
358 return certHash;
359 }
360
361 /**
362 * @return The username to employ if this idcard has authenticationLevel 2. Otherwise <code>null</code>.
363 */
364 public String getUsername() {
365 return username;
366 }
367
368 /**
369 * @return The password to employ if this idcard has authenticationLevel 2. Otherwise <code>null</code>.
370 */
371 public String getPassword() {
372 return password;
373 }
374
375 /**
376 * Signs a document.
377 *
378 * @param document
379 * The document to sign.
380 * @param vault
381 * Object that gives access to credential information used for signing.
382 * @deprecated Use {@link #sign(org.w3c.dom.Document, dk.sosi.seal.pki.SignatureProvider)} instead
383 */
384 @Deprecated
385 public void sign(Document document, CredentialVault vault) {
386 sign(document, new CredentialVaultSignatureProvider(vault));
387 }
388
389 /**
390 * Signs a document.
391 *
392 * @param document
393 * The document to sign.
394 * @param provider
395 * Object that gives access to sign method
396 */
397 public void sign(Document document, SignatureProvider provider) {
398 if(needsSignature) {
399 if(domElement == null) {
400 throw new IllegalStateException("IDCard DOM has not been prepared");
401 }
402 final String[] referenceUris = { IDValues.IDCARD };
403 final SignatureConfiguration configuration = new SignatureConfiguration(referenceUris, IDValues.IDCARD, IDValues.id);
404
405 SignatureUtil.sign(provider, document, configuration);
406
407 lastDOMOperation = SIGNED;
408 needsSignature = false;
409 }
410 }
411
412 /**
413 * Returns a DOM document with the current DOM element representation of this IDCard.
414 */
415 public Element serialize2DOMDocument(SOSIFactory factory, Document doc) {
416 if(domElement == null) {
417 domElement = new IDCardDOMBuilder(factory, doc, this).buildDOMElement();
418 lastDOMOperation = CREATED;
419 }
420 if(!domElement.getOwnerDocument().equals(doc)) {
421 // Import the IDCard DOM element into the new document
422 domElement = (Element)doc.importNode(domElement, true);
423 lastDOMOperation = RE_ASSIGNED;
424 }
425 return domElement;
426 }
427
428 /**
429 * Returns a <code>byte[]</code> containing the bytes that can be used to sign this <code>IDCard</code> object. This
430 * method is useful for situations where the actual signature will be created externally e.g. in the situation where
431 * an actual user has to be prompted for the MOCES signature. The actual signature is computed by:
432 * <p/>
433 * <ol>
434 * <li>Using a <i>RSAwithSHA1</i> algoritm to digest and encrypt the bytes</li>
435 * <li>Base64 encoding the signed byte array</li>
436 * </ol>
437 * <p/>
438 * Please see the {@link #injectSignature(String, X509Certificate)} method for how to embed the signature into
439 * <code>IDCard</code> objects.
440 *
441 * @param doc
442 * The DOM document to calculate the digest from.
443 */
444 public byte[] getBytesForSigning(Document doc) {
445 SignatureConfiguration configuration = new SignatureConfiguration(new String[]{IDValues.IDCARD}, IDValues.IDCARD, IDValues.id);
446 return SignatureUtil.getSignedInfoBytes(doc, configuration);
447 }
448
449 /**
450 * Inserts a XML signature value into the XML document associated with this id card. This method is useful when
451 * signing is done externally, e.g. when signing an <code>IDCard</code> using a MOCES private key.
452 * <p/>
453 * Please refer to {@link #getBytesForSigning(Document)} for details on how to calculate the actual signature value.
454 *
455 * @param signature
456 * A base 64 encoded string containing the RSA encrypted bytes of the <ds:SignedInfo> element
457 * calculated over this <code>IDCard</code>.
458 * @param certificate
459 * The certificate used to create the signature
460 */
461 public void injectSignature(String signature, X509Certificate certificate) {
462 if(getAuthenticationLevel().getLevel() < AuthenticationLevel.VOCES_TRUSTED_SYSTEM.getLevel())
463 throw new ModelException("AuthenticationLevel does not support signature");
464
465 if(domElement == null)
466 throw new ModelException("DOM not initialized");
467 Document document = domElement.getOwnerDocument();
468 SignatureConfiguration configuration = new SignatureConfiguration((SignatureConfiguration.Reference[]) null, IDValues.IDCARD, null);
469 SignatureUtil.injectSignature(document, signature, configuration, certificate, true);
470 lastDOMOperation = SIGNED;
471 needsSignature = false;
472 }
473
474 public X509Certificate getSignedByCertificate() {
475 X509Certificate cert = null;
476 if(getAuthenticationLevel().getLevel() >= AuthenticationLevel.VOCES_TRUSTED_SYSTEM.getLevel()
477 && (domElement != null && !needsSignature)) {
478 cert = SignatureUtil.getCertificateFromSignature(domElement);
479 }
480 return cert;
481 }
482
483 /**
484 * Generates certHash from the idcards certificate.
485 *
486 * @return certHash
487 */
488 public String generateCertHash() {
489 if(domElement == null)
490 throw new ModelException("DOM not initialized");
491
492 X509Certificate certificate = SignatureUtil.getCertificateFromSignature(domElement);
493 return SignatureUtil.getDigestOfCertificate(certificate);
494 }
495
496 // ==========================================
497 // Overridden parts
498 // ==========================================
499
500 /**
501 * @see Object#equals(java.lang.Object)
502 */
503 public boolean equals(Object obj) {
504 return obj == this || obj != null && obj.getClass() == getClass() && obj.hashCode() == hashCode()
505 && getCreatedDate().getTime() / 1000 == ((IDCard)obj).getCreatedDate().getTime() / 1000
506 && getExpiryDate().getTime() / 1000 == ((IDCard)obj).getExpiryDate().getTime() / 1000
507 && getIssuer().equals(((IDCard)obj).getIssuer()) && getVersion().equals(((IDCard)obj).getVersion())
508 && getAuthenticationLevel().equals(((IDCard)obj).getAuthenticationLevel())
509 && safeEquals(getCertHash(), ((IDCard)obj).getCertHash())
510 && safeEquals(getAlternativeIdentifier(), ((IDCard)obj).getAlternativeIdentifier())
511 && safeEquals(getUsername(), ((IDCard)obj).getUsername())
512 && safeEquals(getPassword(), ((IDCard)obj).getPassword());
513 }
514
515 /**
516 * @see Object#hashCode()
517 */
518 public int hashCode() {
519
520 return id.hashCode();
521 }
522
523 private boolean safeEquals(Object a, Object b) {
524 return (a == null && b == null) || a.equals(b);
525 }
526
527 }