001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.security;
020
021import org.jpos.util.Loggeable;
022
023import java.io.PrintStream;
024import java.util.*;
025
026/**
027 * Cryptographic Service Message (CSM for short).
028 *
029 * A message for transporting keys or
030 * related information used to control a keying relationship.
031 * It is typically the contents of ISOField(123).
032 * For more information refer to ANSI X9.17: Financial Institution Key Mangement
033 * (Wholesale).
034 * </p>
035 * @author Hani S. Kirollos
036 * @version $Revision$ $Date$
037 */
038
039@SuppressWarnings("unchecked")
040public class CryptographicServiceMessage implements Loggeable {
041    Map<String, String> fields = new LinkedHashMap<>();
042    String mcl;
043
044    public static final String MCL_RSI = "RSI";
045    public static final String MCL_KSM = "KSM";
046    public static final String MCL_RSM = "RSM";
047    public static final String MCL_ESM = "ESM";
048
049    public static final String TAG_RCV = "RCV";
050    public static final String TAG_ORG = "ORG";
051    public static final String TAG_SVR = "SVR";
052    public static final String TAG_KD  = "KD" ;
053    public static final String TAG_CTP = "CTP";
054    public static final String TAG_CTR = "CTR";
055    public static final String TAG_ERF = "ERF";
056
057    public static class ParsingException extends Exception {
058
059        private static final long serialVersionUID = 6984718759445061L;
060        public ParsingException() {
061            super();
062        }
063        public ParsingException(String detail) {
064            super(detail);
065        }
066    }
067
068    public CryptographicServiceMessage() {
069    }
070
071    /**
072     * Creates a CSM and sets its Message Class
073     * @param mcl message class name. e.g. MCL_KSM, MCL_RSM...
074     */
075    public CryptographicServiceMessage(String mcl) {
076        setMCL(mcl);
077    }
078
079    public void setMCL(String mcl) {
080        this.mcl = mcl;
081    }
082
083    public String getMCL() {
084        return mcl;
085    }
086
087    /**
088     * adds a field to the CSM
089     * @param tag Field Tag
090     * @param content Field Content, can't be null, use an empty string ("") instead
091     * @throws NullPointerException if tag or content is null
092     */
093    public void addField(String tag, String content) {
094        Objects.requireNonNull(tag, "The tag is required");
095        Objects.requireNonNull(content, "The content is required");
096        tag = tag.toUpperCase();
097        fields.put(tag, content);
098    }
099
100    /**
101     * Remove field from CSM
102     * @param tag Field Tag
103     * @throws NullPointerException on null tag
104     */
105    public void removeField (String tag) {
106        Objects.requireNonNull(tag, "The tag is required");
107        fields.remove(tag);
108    }
109
110    /**
111     * Remove fields from CSM
112     * @param tags tag list
113     * @throws NullPointerException on null tag
114     */
115    public void removeFields (String... tags) {
116        for (String tag : tags) {
117            Objects.requireNonNull(tag, "The tag is required");
118            fields.remove(tag);
119        }
120    }
121
122    /**
123     * Returns the field content of a field with the given tag
124     * @param tag
125     * @return field Field Content, or null if tag not found
126     */
127    public String getFieldContent(String tag) {
128        return fields.get(tag.toUpperCase());
129    }
130
131
132    /**
133     * Formats the CSM as a string, suitable for transfer.
134     * This is the inverse of parse
135     * @return the CSM in string format
136     */
137    @Override
138    public String toString() {
139        StringBuilder csm = new StringBuilder();
140        csm.append("CSM(MCL/");
141        csm.append(getMCL());
142        csm.append(" ");
143        for (String tag : fields.keySet()) {
144            csm.append(tag);
145            csm.append("/");
146            csm.append(getFieldContent(tag));
147            csm.append(" ");
148        }
149
150        csm.append(")");
151        return csm.toString();
152    }
153
154
155    /**
156     * dumps CSM basic information
157     * @param p a PrintStream usually supplied by Logger
158     * @param indent indention string, usually suppiled by Logger
159     * @see org.jpos.util.Loggeable
160     */
161    @Override
162    public void dump (PrintStream p, String indent) {
163        String inner = indent + "  ";
164        p.print(indent + "<csm");
165        p.print(" class=\"" + getMCL() + "\"");
166        p.println(">");
167        for (String tag : fields.keySet()) {
168            p.println(inner + "<field tag=\"" + tag + "\" value=\"" + getFieldContent(tag) + "\"/>");
169        }
170        p.println(indent + "</csm>");
171    }
172
173    /**
174     * Parses a csm string
175     * @param csmString
176     * @return CSM object
177     * @throws ParsingException
178     */
179    public static CryptographicServiceMessage parse(String csmString) throws ParsingException {
180        CryptographicServiceMessage csm = new CryptographicServiceMessage();
181        StringTokenizer st = new StringTokenizer(csmString, "() \t\n\r\f");
182        if (!st.nextToken().equalsIgnoreCase("CSM"))
183            throw new ParsingException("Invalid CSM, doesn't start with the \"CSM(\" tag: " + csmString);
184        do {
185            String field = st.nextToken();
186            int separatorIndex = field.indexOf('/');
187            if (separatorIndex > 0) {
188                String tag = field.substring(0, separatorIndex).toUpperCase();
189                String content = "";
190                if (separatorIndex < field.length())
191                    content = field.substring(separatorIndex + 1);
192                if (tag.equalsIgnoreCase("MCL"))
193                    csm.setMCL(content);
194                else {
195                    csm.addField(tag, content);
196                }
197            } else
198                throw new ParsingException("Invalid field, doesn't have a tag: " + field);
199        } while (st.hasMoreTokens());
200        if (csm.getMCL() == null)
201            throw new ParsingException("Invalid CSM, doesn't contain an MCL: " + csmString);
202        return csm;
203    }
204
205}