View Javadoc

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 &lt;ds:SignedInfo&gt; 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 }