001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyrighted [year] [name of copyright owner]". 013 * 014 * Copyright 2014-2016 ForgeRock AS. 015 */ 016 017package org.forgerock.openam.sts.user.invocation; 018 019import com.sun.identity.shared.encode.Base64; 020import org.forgerock.json.JsonValue; 021import org.forgerock.json.resource.ResourceException; 022import org.forgerock.openam.sts.AMSTSConstants; 023import org.forgerock.openam.sts.TokenMarshalException; 024 025import java.io.ByteArrayInputStream; 026import java.io.UnsupportedEncodingException; 027import java.security.cert.CertificateEncodingException; 028import java.security.cert.CertificateException; 029import java.security.cert.CertificateFactory; 030import java.security.cert.X509Certificate; 031 032import static org.forgerock.json.JsonValue.field; 033import static org.forgerock.json.JsonValue.json; 034import static org.forgerock.json.JsonValue.object; 035 036/** 037 * When issuing SAML2 Holder-of-Key assertions, the proof token is usually an X509Certificate. This state must be 038 * specified in the invocation, both to the REST-STS, and in the call to the TokenGenerationService made by the 039 * REST/SOAP STS. This is the analogue to the UseKey element in the WS-Trust defined RequestSecurityToken, which is 040 * defined as 'generally used when the client supplies a public-key that it wishes to be embedded in T as the proof key.' 041 * See http://docs.oasis-open.org/ws-sx/ws-trust/v1.4/errata01/os/ws-trust-1.4-errata01-os-complete.html for details. 042 * The CXF-STS parses out the KeyInfo element included in the UseKey to create the org.apache.cxf.sts.request.ReceivedKey 043 * which encapsulates this public key. Thus the SOAP-STS can use this ReceivedKey to constitute the ProofTokenState, 044 * and the REST-STS will be invoked with the json representation of this class, which can then be forwarded on to the 045 * TokenGenerationService when SAML2 HoK tokens are being issued. 046 * 047 * Note that the WS-Trust spec allows for the UseKey to include symmetric key information, resulting in a SAML2 HoK with 048 * a KeyInfo element which contains symmetric key information. The TokenGenerationService and the REST-STS will not 049 * support proof tokens based on symmetric key information for the moment. 050 * 051 * It may be that PublicKey based proof tokens need to be supported in the future. If so, this class will add a ctor 052 * which takes a PublicKey, and encode which sort of proof-token-state has been provided (e.g. X509Certificate or PublicKey). 053 * 054 * @supported.all.api 055 */ 056public class ProofTokenState { 057 058 /** 059 * Builder class for {@code ProofTokenState} 060 */ 061 public static class ProofTokenStateBuilder { 062 private X509Certificate certificate; 063 064 private ProofTokenStateBuilder() {} 065 066 /** 067 * Adds an {@code X509Certificate} to the builder. 068 * 069 * @param certificate the certificate to add. 070 * @return the builder encapsulating the specified {@code X509Certificate} 071 */ 072 public ProofTokenStateBuilder x509Certificate(X509Certificate certificate) { 073 this.certificate = certificate; 074 return this; 075 } 076 077 /** 078 * Builds the {@code ProofTokenState} 079 * @return the {@code ProofTokenState} 080 * @throws TokenMarshalException 081 */ 082 public ProofTokenState build() throws TokenMarshalException { 083 return new ProofTokenState(this); 084 } 085 } 086 087 private static final String BASE_64_ENCODED_CERTIFICATE = "base64EncodedCertificate"; 088 private static final String X_509 = "X.509"; 089 090 private final X509Certificate certificate; 091 092 private ProofTokenState(ProofTokenStateBuilder builder) throws TokenMarshalException { 093 certificate = builder.certificate; 094 if (certificate == null) { 095 throw new TokenMarshalException(ResourceException.BAD_REQUEST, "the X509Certificate state must be set."); 096 } 097 } 098 099 /** 100 * Gets the {@code X509Certificate} 101 * @return the {@code X509Certificate} 102 */ 103 public X509Certificate getX509Certificate() { 104 return certificate; 105 } 106 107 @Override 108 public boolean equals(Object other) { 109 if (other instanceof ProofTokenState) { 110 ProofTokenState otherState = (ProofTokenState)other; 111 return (certificate.equals(otherState.certificate)); 112 } 113 return false; 114 } 115 116 @Override 117 public int hashCode() { 118 return certificate.hashCode(); 119 } 120 121 @Override 122 public String toString() { 123 return toJson().toString(); 124 } 125 126 /** 127 * Creates a {@code ProofTokenStateBuilder} 128 * @return a {@code ProofTokenStateBuilder} 129 */ 130 public static ProofTokenStateBuilder builder() { 131 return new ProofTokenStateBuilder(); 132 } 133 134 /** 135 * Constructs a {@code ProofTokenState} from the specified {@code JsonValue} representation. 136 * 137 * @param jsonValue the {@code JsonValue} representation to construct the {@code ProofTokenState} from. 138 * @return a {@code ProofTokenState} 139 * @throws TokenMarshalException 140 */ 141 public static ProofTokenState fromJson(JsonValue jsonValue) throws TokenMarshalException { 142 final String certString = jsonValue.get(BASE_64_ENCODED_CERTIFICATE).asString(); 143 try { 144 final X509Certificate x509Certificate = (X509Certificate)CertificateFactory.getInstance(X_509).generateCertificate( 145 new ByteArrayInputStream(Base64.decode(certString.getBytes(AMSTSConstants.UTF_8_CHARSET_ID)))); 146 return ProofTokenState.builder().x509Certificate(x509Certificate).build(); 147 } catch (CertificateException e) { 148 throw new TokenMarshalException(ResourceException.BAD_REQUEST, 149 "Exception caught marshalling from json to X509 cert: " + e, e); 150 } catch (UnsupportedEncodingException e) { 151 throw new TokenMarshalException(ResourceException.BAD_REQUEST, 152 "Exception caught marshalling from json to X509 cert: " + e, e); 153 } 154 } 155 156 /** 157 * Gets the {@code JsonValue} representation of the {@code ProofTokenState} 158 * @return the {@code JsonValue} of the {@code ProofTokenState} 159 * @throws IllegalStateException 160 */ 161 public JsonValue toJson() throws IllegalStateException { 162 try { 163 String base64EncodedCertificate = Base64.encode(certificate.getEncoded()); 164 return json(object(field(BASE_64_ENCODED_CERTIFICATE, base64EncodedCertificate))); 165 } catch (CertificateEncodingException e) { 166 throw new IllegalStateException("Exception getting base64 representation of certificate: " + e, e); 167 } 168 } 169}