├── setup ├── dist ├── rsa.private ├── generate.sh ├── secureKey └── setup_storage_file.go ├── app └── token │ ├── dist │ └── token_test.go ├── restful ├── libsecurity │ ├── dist │ └── config.json ├── accounts-restful │ ├── dist │ └── am_command.go ├── libsecurity-restful │ ├── libsecurity_command.go │ └── libsecurity_test.go ├── password-restful │ ├── pwd_command.go │ ├── pwd_restful.go │ └── pwd_restful_test.go ├── otp-restful │ ├── otp_command.go │ └── otp_restful.go ├── storage-restful │ ├── secureStorage_command.go │ ├── secureStorage_restful.go │ └── secureStorage_restful_test.go ├── ocra-restful │ └── ocra_command.go ├── acl-restful │ └── acl_command.go └── common-restful │ └── common_restful.go ├── .gitignore ├── .gitattributes ├── swagger-dist ├── images │ ├── conf.png │ ├── logo.jpg │ ├── iot_logo.png │ ├── throbber.gif │ ├── logo_small.png │ ├── explorer_icons.png │ └── forewind_fav_icon.png ├── fonts │ ├── droid-sans-v6-latin-700.eot │ ├── droid-sans-v6-latin-700.ttf │ ├── droid-sans-v6-latin-700.woff │ ├── droid-sans-v6-latin-700.woff2 │ ├── droid-sans-v6-latin-regular.eot │ ├── droid-sans-v6-latin-regular.ttf │ ├── droid-sans-v6-latin-regular.woff │ └── droid-sans-v6-latin-regular.woff2 ├── lib │ ├── jquery.slideto.min.js │ ├── jquery.wiggle.min.js │ ├── jquery.ba-bbq.min.js │ ├── highlight.7.3.pack.js │ └── shred │ │ └── content.js ├── o2c.html ├── security.json ├── secureKey ├── server.csr ├── server.crt ├── data.txt ├── css │ ├── typography.css │ └── reset.css ├── server.key ├── forewind │ └── app │ │ └── v1 │ │ ├── libsecurity │ │ ├── securestorage │ │ └── password ├── key.private └── index.html ├── CONTRIBUTING.md ├── logger ├── logger_example_test.go └── logger.go ├── entity └── entityManagmentUtils_test.go ├── otp ├── gauth_js.go ├── otpTotp_test.go ├── otp_examples_test.go ├── otpExternal_test.go ├── otp_test.go └── otpHotp_test.go ├── storage ├── storage_example_test.go └── secureStorage_test.go ├── defs ├── libsecurityCommonForTest.go └── libsecurityCommon.go ├── acl ├── aclEntry.go ├── acl_example_test.go └── aclEntry_test.go ├── salt ├── salt_examples_test.go ├── salt.go └── salt_test.go ├── password └── password_example_test.go └── accounts └── accountManagement_test.go /setup/dist: -------------------------------------------------------------------------------- 1 | ../swagger-dist -------------------------------------------------------------------------------- /app/token/dist: -------------------------------------------------------------------------------- 1 | ../../swagger-dist -------------------------------------------------------------------------------- /restful/libsecurity/dist: -------------------------------------------------------------------------------- 1 | ../../swagger-dist -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | *.sh 3 | tmp 4 | *.out 5 | -------------------------------------------------------------------------------- /restful/accounts-restful/dist: -------------------------------------------------------------------------------- 1 | ../../swagger-dist -------------------------------------------------------------------------------- /setup/rsa.private: -------------------------------------------------------------------------------- 1 | ../swagger-dist/rsa.private -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .rb linguist-language=Go 2 | swagger-dist/* linguist-vendored 3 | -------------------------------------------------------------------------------- /setup/generate.sh: -------------------------------------------------------------------------------- 1 | go run setup_storage_file.go -password="Im the root12#^" 2 | #cp data.txt dist 3 | -------------------------------------------------------------------------------- /swagger-dist/images/conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/images/conf.png -------------------------------------------------------------------------------- /swagger-dist/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/images/logo.jpg -------------------------------------------------------------------------------- /swagger-dist/images/iot_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/images/iot_logo.png -------------------------------------------------------------------------------- /swagger-dist/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/images/throbber.gif -------------------------------------------------------------------------------- /swagger-dist/images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/images/logo_small.png -------------------------------------------------------------------------------- /swagger-dist/images/explorer_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/images/explorer_icons.png -------------------------------------------------------------------------------- /swagger-dist/images/forewind_fav_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/images/forewind_fav_icon.png -------------------------------------------------------------------------------- /swagger-dist/fonts/droid-sans-v6-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/fonts/droid-sans-v6-latin-700.eot -------------------------------------------------------------------------------- /swagger-dist/fonts/droid-sans-v6-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/fonts/droid-sans-v6-latin-700.ttf -------------------------------------------------------------------------------- /swagger-dist/fonts/droid-sans-v6-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/fonts/droid-sans-v6-latin-700.woff -------------------------------------------------------------------------------- /swagger-dist/fonts/droid-sans-v6-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/fonts/droid-sans-v6-latin-700.woff2 -------------------------------------------------------------------------------- /swagger-dist/fonts/droid-sans-v6-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/fonts/droid-sans-v6-latin-regular.eot -------------------------------------------------------------------------------- /swagger-dist/fonts/droid-sans-v6-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/fonts/droid-sans-v6-latin-regular.ttf -------------------------------------------------------------------------------- /swagger-dist/fonts/droid-sans-v6-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/fonts/droid-sans-v6-latin-regular.woff -------------------------------------------------------------------------------- /swagger-dist/fonts/droid-sans-v6-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-security-innovation/libsecurity-go/HEAD/swagger-dist/fonts/droid-sans-v6-latin-regular.woff2 -------------------------------------------------------------------------------- /restful/libsecurity/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "accountManager": "full", 3 | "otp": "basic", 4 | "acl": "basic", 5 | "appAcl": "none", 6 | "um": "basic", 7 | "ocra": "basic", 8 | "password": "basic", 9 | "secureStorage": "basic" 10 | } 11 | -------------------------------------------------------------------------------- /swagger-dist/lib/jquery.slideto.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery); 2 | -------------------------------------------------------------------------------- /swagger-dist/o2c.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swagger-dist/security.json: -------------------------------------------------------------------------------- 1 | {"apiVersion":"2.02","apis":[{"description":"","path":"/../forewind/app/v1/account-manager"},{"description":"","path":"/../forewind/app/v1/entity"},{"description":"","path":"/../forewind/app/v1/acl"},{"description":"","path":"/../forewind/app/v1/otp"},{"description":"","path":"/../forewind/app/v1/ocra"},{"description":"","path":"/../forewind/app/v1/password"},{"description":"Secure Storage","path":"/../forewind/app/v1/securestorage"},{"description":"The Security Tool","path":"/../forewind/app/v1/libsecurity"}],"info":{"description":"","title":"Libsecurity API"},"swaggerVersion":"1.2"} -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | To contribute, please submit a Pull Request. The only thing that we ask that you do 2 | is a very simple step, include a line modeled after this line: 3 | “DCO 1.1 Signed-off-by: Random J Developer <random@developer.org>" 4 | as part of your pull request comments. By doing this one simple thing you are telling 5 | the community that you wrote the code you are contributing or have the right to pass 6 | on the code that you are contributing. The 7 | DCO 1.1 referenced is the Developer Certificate of Origin that is used in the Linux Kernel community, you can find it [here](http://elinux.org/Developer_Certificate_Of_Origin "DCO 1.1"). 8 | 9 | Thank you! 10 | -------------------------------------------------------------------------------- /swagger-dist/lib/jquery.wiggle.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Wiggle 3 | Author: WonderGroup, Jordan Thomas 4 | URL: http://labs.wondergroup.com/demos/mini-ui/index.html 5 | License: MIT (http://en.wikipedia.org/wiki/MIT_License) 6 | */ 7 | jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('
').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);} 8 | if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});}; -------------------------------------------------------------------------------- /setup/secureKey: -------------------------------------------------------------------------------- 1 | MIICXAIBAAKBgQC4xn/4m0Hs2vnbVjMCmpcTAN0Zx0fhzkVJFF5TPxPX1w3gthrG 2 | zzoI5xm4CWkZauXXhViR9FRtWnI7NqesYJsvbBxdj77XS0bmRNrs8WsectTVfxSO 3 | 99BuysINhxnN5IV560R23qS3Pur1u+OfZxGUTT+hc7bgieY+GQao+/Zq8QIDAQAB 4 | AoGAGABo5SE9ygeTxyMGU0NDdot5LP0O4E0MAPctXYDnEfV0bwkYp1SJxFhIpfhK 5 | 96tEGd/PZn4nbWD/T6PE2gZegez1SFIMguBs4dRYNg5FVEl6/r2FaT+DbqGHVGdl 6 | TtSnONPis1c1AgAHG8dYZ8Ep30qRUpCFKF5cpYvecW1gIfECQQDdKLDNvder0+g1 7 | bvyxck0Wt9x+jYpz6DMPbv/CCmxcMcf+By/uV2N2I95U77Dx+KIZVe54x+Kyuzhs 8 | GOHf2RnFAkEA1eJ3KQEe/U9hQoMcl1GK4KkZlY7X3HKeZeRcr6CuL7RFTf57Dj4x 9 | 7Cs0raVSN9KYRnqM4lfihRJtIvXbry2bPQJAB2IwZO4NoUL14bLO5O23/SYuSltX 10 | X/9ElSNTjprie0F+N9DtnLt2Kp+P8K3/7lQqtUXVLFsgCm+Ntq4t/Fc3MQJAW5US 11 | 7O3b9R8uSCqX7Xsu+i+uEsoNSAmUJZD41wL4Utjf7BuRWXHsMnH1pnzpjZmRNmx3 12 | Mtvq1IwlSxj3LjVbCQJBALJdLki0BdNVBEEmfwbwaED6Ujx9LlDcY9hDfcc6D/Eo 13 | FtHYRHBulG59peDn1rT0tkl9hUsVA7EC+f8SOlJwrxo= 14 | -------------------------------------------------------------------------------- /swagger-dist/secureKey: -------------------------------------------------------------------------------- 1 | MIICXAIBAAKBgQC4xn/4m0Hs2vnbVjMCmpcTAN0Zx0fhzkVJFF5TPxPX1w3gthrG 2 | zzoI5xm4CWkZauXXhViR9FRtWnI7NqesYJsvbBxdj77XS0bmRNrs8WsectTVfxSO 3 | 99BuysINhxnN5IV560R23qS3Pur1u+OfZxGUTT+hc7bgieY+GQao+/Zq8QIDAQAB 4 | AoGAGABo5SE9ygeTxyMGU0NDdot5LP0O4E0MAPctXYDnEfV0bwkYp1SJxFhIpfhK 5 | 96tEGd/PZn4nbWD/T6PE2gZegez1SFIMguBs4dRYNg5FVEl6/r2FaT+DbqGHVGdl 6 | TtSnONPis1c1AgAHG8dYZ8Ep30qRUpCFKF5cpYvecW1gIfECQQDdKLDNvder0+g1 7 | bvyxck0Wt9x+jYpz6DMPbv/CCmxcMcf+By/uV2N2I95U77Dx+KIZVe54x+Kyuzhs 8 | GOHf2RnFAkEA1eJ3KQEe/U9hQoMcl1GK4KkZlY7X3HKeZeRcr6CuL7RFTf57Dj4x 9 | 7Cs0raVSN9KYRnqM4lfihRJtIvXbry2bPQJAB2IwZO4NoUL14bLO5O23/SYuSltX 10 | X/9ElSNTjprie0F+N9DtnLt2Kp+P8K3/7lQqtUXVLFsgCm+Ntq4t/Fc3MQJAW5US 11 | 7O3b9R8uSCqX7Xsu+i+uEsoNSAmUJZD41wL4Utjf7BuRWXHsMnH1pnzpjZmRNmx3 12 | Mtvq1IwlSxj3LjVbCQJBALJdLki0BdNVBEEmfwbwaED6Ujx9LlDcY9hDfcc6D/Eo 13 | FtHYRHBulG59peDn1rT0tkl9hUsVA7EC+f8SOlJwrxo= 14 | -------------------------------------------------------------------------------- /swagger-dist/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIChjCCAW4CAQAwQTELMAkGA1UEBhMCSUwxFDASBgNVBAoMC2R1bW15LmxvY2Fs 3 | MRwwGgYDVQQDDBNpby10ZXN0LmR1bW15LmxvY2FsMIIBIjANBgkqhkiG9w0BAQEF 4 | AAOCAQ8AMIIBCgKCAQEAzGAOB+BJwqLXF8nm/iKfdvuc2QOgmKw3qllNkQEdWbTa 5 | 7Wn/5opTkXbS7sZwr59JTfeyB9LKNufjDRBbrUCTdpcVqN1D4TgDywyl/NR0AhV+ 6 | r8NuPnogR3PZU83lvMi1ErQ0w75N3K0FLaOO9KMhkfOffOpn+SS7KOYSqbN7xaNj 7 | gkP+9XM4H5nEtyyhaeQy4oWSscSCu9lx1dG3YlewyL5+66gwkv4HJWNnsAGPEDAB 8 | VhVWh+uYbV4EhH21If/QYfZFVRtgWEva91x6xePXvIClk+wKhNcgMTpBt2F5m70m 9 | TUH9V1nhMnWdUitWd5Gvb1viKg4AqqhL8nEDto+ErwIDAQABoAAwDQYJKoZIhvcN 10 | AQELBQADggEBADL6+V712HZ5rTds0pSouYZOfJTR6a2azDZQtMr2inhYOfE2dUjL 11 | k7OnGUeoFBkfh7fq3g2jDCqqI9m29mSORb/3KhSkavA5j6/nfPAnmn8rdNqRoCec 12 | V2TPL+36RUWeF2AZZkFv6pS9fkiAMlIs4E9kwfEXljnjO6yuvR1Wc+lgitvWfJKU 13 | qNuK54d65goEZJvxnnVyaCfkwibdOm0PiMRzG2S7kAul66fYlHeAl22AdczsWWDQ 14 | TS9W9D0x1XvgZqeiCMg53bz0LmxwUrkK+Zrt+GmzHpJJSxj24FzPUJec4IwN0moT 15 | ex29eWZjIV5er2Tf49ykQGRrySsYOUnpVOQ= 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /logger/logger_example_test.go: -------------------------------------------------------------------------------- 1 | package logger_test 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 10 | ) 11 | 12 | // This example show the following: 13 | // - Discard Trace messages 14 | // - Write Info messages to stdout 15 | // - Write Warning to stdout and log file 16 | // - Write Error to stderr and log file 17 | func Example_logger() { 18 | fileName := "log-file.txt" 19 | os.Remove(fileName) 20 | file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 21 | if err != nil { 22 | log.Fatalln("Failed to open log file", fileName, ":", err) 23 | } 24 | 25 | multiW := io.MultiWriter(file, os.Stdout) 26 | multiE := io.MultiWriter(file, os.Stderr) 27 | logger.Init(ioutil.Discard, os.Stdout, multiW, multiE) 28 | 29 | logger.Trace.Println("Example: I have something standard to say") 30 | logger.Info.Println("Example: Special Information") 31 | logger.Warning.Println("Example: There is something you need to know about") 32 | logger.Error.Println("Example: Something has failed") 33 | } 34 | -------------------------------------------------------------------------------- /swagger-dist/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC/jCCAeYCCQDjmfl0P3G5uDANBgkqhkiG9w0BAQsFADBBMQswCQYDVQQGEwJJ 3 | TDEUMBIGA1UECgwLZHVtbXkubG9jYWwxHDAaBgNVBAMME2lvLXRlc3QuZHVtbXku 4 | bG9jYWwwHhcNMTYwMTA0MDkyMTE5WhcNMjYwMTAxMDkyMTE5WjBBMQswCQYDVQQG 5 | EwJJTDEUMBIGA1UECgwLZHVtbXkubG9jYWwxHDAaBgNVBAMME2lvLXRlc3QuZHVt 6 | bXkubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMYA4H4EnC 7 | otcXyeb+Ip92+5zZA6CYrDeqWU2RAR1ZtNrtaf/milORdtLuxnCvn0lN97IH0so2 8 | 5+MNEFutQJN2lxWo3UPhOAPLDKX81HQCFX6vw24+eiBHc9lTzeW8yLUStDTDvk3c 9 | rQUto470oyGR85986mf5JLso5hKps3vFo2OCQ/71czgfmcS3LKFp5DLihZKxxIK7 10 | 2XHV0bdiV7DIvn7rqDCS/gclY2ewAY8QMAFWFVaH65htXgSEfbUh/9Bh9kVVG2BY 11 | S9r3XHrF49e8gKWT7AqE1yAxOkG3YXmbvSZNQf1XWeEydZ1SK1Z3ka9vW+IqDgCq 12 | qEvycQO2j4SvAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACkY0eJKdzgRF4EjTZ9P 13 | G9xb3CHuvsrg/QsLVxbQcwIyKBEKVXMNSH6EK8SUIn1UDeDepggwMYq9u689H4/V 14 | dTY6OirfoQh8UtKe1GQCY2gRl8z6hoMsD2C/0Piy79x7mKzTtgKqakAV2dPehfk8 15 | udanV8uhfcF/DwXO5qm+0UdLDJCsP2f3Kw4fcOo9IDHPjcIv1b7dWAXSaSkBrhZa 16 | sWh/YIZG4OHW+JFFx1tOtl3tX6MdrklT04IsZ0mL5iyzTvVIBHLc5ml/4IpOppkR 17 | wZ09M2+TB7fTcknQC1AMBVSbHelRwFN2orum2JYZrXGKV2RLEf5DtqHVulQnAS5F 18 | 8N8= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /swagger-dist/data.txt: -------------------------------------------------------------------------------- 1 | {"Salt":"/XJQj3+3LDQ=","Sign":"czJ9qKW4FCR1kvHQLFew1W5I4ZuYhcSmv8BbIAFj/yg=","Data":{"Dh2J2RRr7Y63PA9I+/a9oyPBmvj6DUgqzbxt8ZdvH6Y=":"NVnWHfLEmoJnUagrKWABZA==","TlZuV0hmTEVtb0puVWFncq58m/tTjdE5k5/PGGqOsaE=":"JIz1ycPTUU3oPA+WdoNYU2CAeSxQAkqCLTSS12SYYmUdxCc6SuKhscaYC+QiKnL1LBO6BzTW795INpAxrpq/6UYHzZApGVp+MIXzCZypkUwUjWwjcenS34F8EjJ71pRxm/VXR+MsfOp1AEXnOlskkaIPDcIiCm2uUiL5MyJM55C5z32hM6O+ScysSpxnm7fWBAEwDQqoyY3wHvLWyQztXW3hGbTETOIhsoXTTtc5kTSgKolqJtNhm7y662sMieNfFfGBKmdSlyJvwrV7sLYpECK1LnEJBjB4pFnL5L6BcMiljxQl2zhb5M8Ck5eQmJRrnAF2A/BUJ1lpOcpyt5D8dQ3E0FwYmrIE9uvgD4MqhI3UaXbMcezvWtfm4c7aq5Ik+hImMHXLhLhSGfIOXPgkJdtURADAm6PBzy0R9nA9GfvGOiUq0tnGnl8XBHMvcTVTzyuAvwwHlEWWHbGs1bwqNw==","WHJSNitKd2kwdzFSTHhJTr8OoBk+UbwmJB1am0NkyEvoVanCrpDRi3jI1AaHLF6o":"TpO9nP07S8Nrr3hjAQOPj0T964fetcdfr2x9Dd8ItoqFAo/+9e2zzD83piqLrvj4VvqtAS8Ei2eF1Z6b7dK/Ig==","ZmVLTWlWaGJ0WEo2ZzdVeBLbmlar2rXICPAa93HP1pZhhV7hcZIdSWxVVkH66QE3":"SzLjPVrUY48AESK2tcyL9JQgrE37wggcVtrSyVs6Kr+kgN5QAYYEeEQUjgAWcvNjE3/8MrUQI/fi0sRhFD7aQtaWdXvJvzHM9WtxP3aSxXzuAKAPm0a3HcH8v5xXw+/invn4EuyH3Rnt/9rKcWWdzURd6+KFmfXCe7W3vaou0pi0LKbKMrHOeEmvbK4VLJNYEI19rDt0CdXlFrFFiayusfzmkpIgAZ+I5OIOqol17FNLZ+osEoyVaY9FckctRt5WyhaDs3OFDHe+B73g+SabZsPRG7DWyFnZswIps8kypTrkpWI0Wg8kTBr/HqgGrL5k2zsD+3nr80C/qLwGk5ZW3rFZoDc1CB8SImBKfySZlTwy9kHtuT/E60GmPIVkLC6mPFlTPl+jEmK0IW4KZVAQ/hN/3IzHgyk5458FXb6M21zQus8VdMMqv8K2fSO2Xrx5fJ/ngxNHrw+sRGqO3QQLguD4ieqNHo6vJcWB75bql8PkCk2K5ovzUPgJa3wK1gwisN/RViy1LUEINg39xxMRwg==","gyEMyn7EU4gbsCqzmrDAvLhJ/nrjRIRuyc7NtgYVAzQ=":"XrR6+Jwi0w1RLxINZvnmqg==","oIej+hRuRXpy4m282C3iAbWjL0MmSsSkTXTAtGCFhVs=":"feKMiVhbtXJ6g7UxFlvVXg=="},"Version":"V 1.2"} -------------------------------------------------------------------------------- /swagger-dist/css/typography.css: -------------------------------------------------------------------------------- 1 | /* droid-sans-regular - latin */ 2 | @font-face { 3 | font-family: 'Droid Sans'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url('../fonts/droid-sans-v6-latin-regular.eot'); /* IE9 Compat Modes */ 7 | src: local('Droid Sans'), local('DroidSans'), 8 | url('../fonts/droid-sans-v6-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 9 | url('../fonts/droid-sans-v6-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ 10 | url('../fonts/droid-sans-v6-latin-regular.woff') format('woff'), /* Modern Browsers */ 11 | url('../fonts/droid-sans-v6-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ 12 | url('../fonts/droid-sans-v6-latin-regular.svg#DroidSans') format('svg'); /* Legacy iOS */ 13 | } 14 | /* droid-sans-700 - latin */ 15 | @font-face { 16 | font-family: 'Droid Sans'; 17 | font-style: normal; 18 | font-weight: 700; 19 | src: url('../fonts/droid-sans-v6-latin-700.eot'); /* IE9 Compat Modes */ 20 | src: local('Droid Sans Bold'), local('DroidSans-Bold'), 21 | url('../fonts/droid-sans-v6-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 22 | url('../fonts/droid-sans-v6-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ 23 | url('../fonts/droid-sans-v6-latin-700.woff') format('woff'), /* Modern Browsers */ 24 | url('../fonts/droid-sans-v6-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */ 25 | url('../fonts/droid-sans-v6-latin-700.svg#DroidSans') format('svg'); /* Legacy iOS */ 26 | } 27 | -------------------------------------------------------------------------------- /entity/entityManagmentUtils_test.go: -------------------------------------------------------------------------------- 1 | package entityManagement 2 | 3 | import ( 4 | "fmt" 5 | 6 | am "github.com/ibm-security-innovation/libsecurity-go/accounts" 7 | // "github.com/ibm-security-innovation/libsecurity-go/acl" 8 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 9 | "github.com/ibm-security-innovation/libsecurity-go/ocra" 10 | "github.com/ibm-security-innovation/libsecurity-go/otp" 11 | "github.com/ibm-security-innovation/libsecurity-go/password" 12 | ) 13 | 14 | func GenerateUserData(el *EntityManager, usersName []string, secret []byte, salt []byte) { 15 | el.AddUser(usersName[0]) 16 | el.AddResource("r"+usersName[0]) 17 | amData, _ := am.NewUserAm(am.SuperUserPermission, secret, salt, false) 18 | el.AddPropertyToEntity(usersName[0], defs.AmPropertyName, amData) 19 | otpData, _ := otp.NewSimpleOtpUser(secret, false) 20 | el.AddPropertyToEntity(usersName[0], defs.OtpPropertyName, otpData) 21 | pwdData, _ := password.NewUserPwd(secret, salt, false) 22 | el.AddPropertyToEntity(usersName[0], defs.PwdPropertyName, pwdData) 23 | ocraData, _ := ocra.NewOcraUser([]byte("ABCD1234"), "OCRA-1:HOTP-SHA512-8:C-QH08-T1M-S064-PSHA256") 24 | el.AddPropertyToEntity(usersName[0], defs.OcraPropertyName, ocraData) 25 | 26 | el.AddUser(usersName[1]) 27 | el.AddPropertyToEntity(usersName[1], defs.OtpPropertyName, otpData) 28 | } 29 | 30 | func GenerateGroupList(el *EntityManager, usersName []string) { 31 | for i := 0; i < 3; i++ { 32 | groupName := fmt.Sprintf("group-%d", i+1) 33 | el.AddGroup(groupName) 34 | for j := 0; j < i; j++ { 35 | el.AddUserToGroup(groupName, usersName[j]) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /otp/gauth_js.go: -------------------------------------------------------------------------------- 1 | package otp 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "encoding/hex" 7 | "fmt" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // Porting of the JS implementation of Google Authenticator 13 | // presented in http://fiddle.jshell.net/russau/ch8PK/show/light/?secret=abcd 14 | 15 | func base32tohex(base32 string) string { 16 | base32chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" 17 | bits := "" 18 | hex := "" 19 | base32u := strings.ToUpper(base32) 20 | 21 | for i := 0; i < len(base32); i++ { 22 | val := strings.Index(base32chars, string(base32u[i])) 23 | // fmt.Println(fmt.Sprintf("%05b", val)) 24 | bits += fmt.Sprintf("%05b", val) 25 | } 26 | 27 | for i := 0; (i + 4) <= len(bits); i += 4 { 28 | chunk := bits[i : i+4] 29 | cval, _ := strconv.ParseInt(chunk, 2, 8) 30 | chex := fmt.Sprintf("%0x", cval) 31 | // fmt.Println(chunk, cval, chex) 32 | hex = hex + chex 33 | } 34 | return hex 35 | } 36 | 37 | func jsOtp(base32secret string, epoch int64) string { 38 | key := base32tohex(base32secret) 39 | time := fmt.Sprintf("%016x", epoch/30) 40 | //fmt.Println(key,epoch,time) 41 | 42 | k, _ := hex.DecodeString(key) 43 | v, _ := hex.DecodeString(time) 44 | hm := hmac.New(sha1.New, k) 45 | hm.Write(v) 46 | hmcout := hex.EncodeToString(hm.Sum(nil)) 47 | 48 | offset, _ := strconv.ParseInt(hmcout[len(hmcout)-1:], 16, 64) 49 | substr := hmcout[offset*2 : offset*2+8] 50 | psubstr, _ := strconv.ParseInt(substr, 16, 64) 51 | pmask, _ := strconv.ParseInt("7fffffff", 16, 64) 52 | otpEx := fmt.Sprintf("%d", psubstr&pmask) 53 | // fmt.Println(offset,otp_ex[len(otp_ex)-6:]) 54 | return otpEx[len(otpEx)-6:] 55 | } 56 | -------------------------------------------------------------------------------- /swagger-dist/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAzGAOB+BJwqLXF8nm/iKfdvuc2QOgmKw3qllNkQEdWbTa7Wn/ 3 | 5opTkXbS7sZwr59JTfeyB9LKNufjDRBbrUCTdpcVqN1D4TgDywyl/NR0AhV+r8Nu 4 | PnogR3PZU83lvMi1ErQ0w75N3K0FLaOO9KMhkfOffOpn+SS7KOYSqbN7xaNjgkP+ 5 | 9XM4H5nEtyyhaeQy4oWSscSCu9lx1dG3YlewyL5+66gwkv4HJWNnsAGPEDABVhVW 6 | h+uYbV4EhH21If/QYfZFVRtgWEva91x6xePXvIClk+wKhNcgMTpBt2F5m70mTUH9 7 | V1nhMnWdUitWd5Gvb1viKg4AqqhL8nEDto+ErwIDAQABAoIBABH/J10kL/qWslAi 8 | 9FdOnzIwB+jIO/0VtTUn9f+d1ILQvTKI0gLwDVnS19I04Tll0mOuOxwbQLlDBChj 9 | V19TP2kC9uJO0apkb1kS/Q7denWwhN5fXDbeNDd2iR2rFN7h4agK/FZ8TIjxJ3KU 10 | Y4NBVjRu+o1aattyk5SPZfdRmcWWbEfe3JJ7q2F9oh64JhVp53Xe3VTHDbgTKZoH 11 | 3Y47ER7lu173CimoF9ufCfCgD8QLBfmFVXOISZar47m9CRIvRlMm4P+yTygq+6zG 12 | L2a25VLCt9lr5cHkYApq7fvW2fAccQwHOOOM1UZlBRxbijsXOhJYDDq8GsQqb+YN 13 | 5RGLaTECgYEA9pjhdWAL2zxidORzkBodPOBhxWcW89go1YmNVquaKDeh9dqusDzk 14 | OUr887LZpCUIbRB/LH4PJ/GvbQnNhu98joWuEP9Hinij3w+gOM9BxwUnA2Z6Y1Ox 15 | 3T4wdJZSK7ItnRGGPuAmybOdxwR5jZ87EDG5/lEi/t9pEVk4icA7v5kCgYEA1CsH 16 | 72sS6G+oiPy4O8iGYPg7s+8O3INj4e8io201bBboLdTwDAnhIkhIqa3XNb+xAT1S 17 | bZq1CuazaMjzCf1xckhJ+N4hmsPs1lfiC1xs67Dqh5mPUmKwgntQDJ5AEMlLWSBl 18 | Vb4N7RPVmMKCVoCHuJbVoeThW9UKnYsUOqQyM4cCgYBZqHQwzN8Csw3JGla6CgSf 19 | YaWqtDSwK+OM4Bo3bOT7wl5smTnycfE4s38My/Y9rAlIPjs+3gZ8q27w4iOL7/8F 20 | gq0rGOXV/s72ByRmA1UTG1h38AaHnqKIolfTy+PMZ1gWR4zbx3vS0i+HcKURTNeY 21 | p7vML4vdxzTYTew0iWQv4QKBgCpEaFBKBHpJxEmGDy3ZxOuMiFhQR6Wmhz97ZGsq 22 | VnoyFg8LXMcJMNCjldY3y6fpiFm23bT4HRkO3K8DWWekFfWucQ4jMo6ieYw59c5N 23 | m2VY2WAqN2MyRfe4mTnqTNkLQPeAfK+YLS4NxX+pQE37TQ54/hZCgqLIzlSUYHNO 24 | QrIBAoGAZLqwfluSDRgUgNSk6++efb6N6hYqIUA259wF2Z+rN2Y0kqR+OTwcScTx 25 | OfN7MGUChDCkFVLvgEjHeK8jKOprk0GqQyN5s4TMNZrDOIWj+K8Vo5hewEDLwzgt 26 | zh9+nNrNwLrHgWn65JeXFe62Ptrb18OWc5XNafUBaWVqK6DXuhc= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /swagger-dist/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ 2 | html, 3 | body, 4 | div, 5 | span, 6 | applet, 7 | object, 8 | iframe, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | p, 16 | blockquote, 17 | pre, 18 | a, 19 | abbr, 20 | acronym, 21 | address, 22 | big, 23 | cite, 24 | code, 25 | del, 26 | dfn, 27 | em, 28 | img, 29 | ins, 30 | kbd, 31 | q, 32 | s, 33 | samp, 34 | small, 35 | strike, 36 | strong, 37 | sub, 38 | sup, 39 | tt, 40 | var, 41 | b, 42 | u, 43 | i, 44 | center, 45 | dl, 46 | dt, 47 | dd, 48 | ol, 49 | ul, 50 | li, 51 | fieldset, 52 | form, 53 | label, 54 | legend, 55 | table, 56 | caption, 57 | tbody, 58 | tfoot, 59 | thead, 60 | tr, 61 | th, 62 | td, 63 | article, 64 | aside, 65 | canvas, 66 | details, 67 | embed, 68 | figure, 69 | figcaption, 70 | footer, 71 | header, 72 | hgroup, 73 | menu, 74 | nav, 75 | output, 76 | ruby, 77 | section, 78 | summary, 79 | time, 80 | mark, 81 | audio, 82 | video { 83 | margin: 0; 84 | padding: 0; 85 | border: 0; 86 | font-size: 100%; 87 | font: inherit; 88 | vertical-align: baseline; 89 | } 90 | /* HTML5 display-role reset for older browsers */ 91 | article, 92 | aside, 93 | details, 94 | figcaption, 95 | figure, 96 | footer, 97 | header, 98 | hgroup, 99 | menu, 100 | nav, 101 | section { 102 | display: block; 103 | } 104 | body { 105 | line-height: 1; 106 | } 107 | ol, 108 | ul { 109 | list-style: none; 110 | } 111 | blockquote, 112 | q { 113 | quotes: none; 114 | } 115 | blockquote:before, 116 | blockquote:after, 117 | q:before, 118 | q:after { 119 | content: ''; 120 | content: none; 121 | } 122 | table { 123 | border-collapse: collapse; 124 | border-spacing: 0; 125 | } 126 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | // Package logger : The logger package contains the implementation of log handling. 2 | package logger 3 | 4 | import ( 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | ) 10 | 11 | var ( 12 | // Trace : write trace information to the logger 13 | Trace *log.Logger 14 | // Info : write info and trace information to the logger 15 | Info *log.Logger 16 | // Warning : write warnings, info and trace information to the logger 17 | Warning *log.Logger 18 | // Error : write errors, warnings, info and trace information to the logger 19 | Error *log.Logger 20 | ) 21 | 22 | func init() { 23 | Init(ioutil.Discard, ioutil.Discard, os.Stdout, os.Stderr) 24 | } 25 | 26 | // Init : initialize the logger 27 | func Init(traceHandle io.Writer, infoHandle io.Writer, warningHandle io.Writer, errorHandle io.Writer) { 28 | Trace = log.New(traceHandle, "TRACE: ", 29 | log.Ldate|log.Ltime|log.Lshortfile) 30 | 31 | Info = log.New(infoHandle, "INFO: ", 32 | log.Ldate|log.Ltime|log.Lshortfile) 33 | 34 | Warning = log.New(warningHandle, "WARNING: ", 35 | log.Ldate|log.Ltime|log.Lshortfile) 36 | 37 | Error = log.New(errorHandle, "ERROR: ", 38 | log.Ldate|log.Ltime|log.Lshortfile) 39 | } 40 | 41 | // type UtilLogger { 42 | // _trace *log.Logger 43 | // _info *log.Logger 44 | // _warning *log.Logger 45 | // _error *log.Logger 46 | // } 47 | 48 | // func (logger UtilLogger *) New(traceHandle io.Writer, infoHandle io.Writer, warningHandle io.Writer, errorHandle io.Writer) { 49 | // logger._trace = log.New(traceHandle, "TRACE: ", 50 | // log.Ldate|log.Ltime|log.Lshortfile) 51 | 52 | // logger._info = log.New(infoHandle, "INFO: ", 53 | // log.Ldate|log.Ltime|log.Lshortfile) 54 | 55 | // logger._warning = log.New(warningHandle, "WARNING: ", 56 | // log.Ldate|log.Ltime|log.Lshortfile) 57 | 58 | // logger._error = log.New(errorHandle, "ERROR: ", 59 | // log.Ldate|log.Ltime|log.Lshortfile) 60 | 61 | // } 62 | 63 | // func (logger UtilLogger *) Info(msg) { 64 | // logger._info.Println(msg) 65 | // } 66 | -------------------------------------------------------------------------------- /storage/storage_example_test.go: -------------------------------------------------------------------------------- 1 | package storage_test 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | ss "github.com/ibm-security-innovation/libsecurity-go/storage" 10 | ) 11 | 12 | const ( 13 | keyFmt = "The key is: %v" 14 | dataFmt = "The data is (sum of key digits): %v" 15 | ) 16 | 17 | var ( 18 | aesSecretLen = 32 19 | ) 20 | 21 | func init() { 22 | } 23 | 24 | func generateSecureStorage() (*ss.SecureStorage, []byte) { 25 | var secret []byte 26 | 27 | secret = make([]byte, aesSecretLen) 28 | io.ReadFull(rand.Reader, secret) 29 | storage, _ := ss.NewStorage(secret, false) 30 | for i := 0; i < 10; i++ { 31 | keyText := fmt.Sprintf(keyFmt, i) 32 | dataText := fmt.Sprintf(dataFmt, i*10+1) 33 | storage.AddItem(keyText, dataText) 34 | } 35 | return storage, secret 36 | } 37 | 38 | func playWithSecureStorage(storage *ss.SecureStorage, secret []byte) { 39 | fileName := "try.txt" 40 | defer os.Remove(fileName) 41 | err := storage.StoreInfo(fileName) 42 | if err != nil { 43 | fmt.Println("Error while saving:", err) 44 | } 45 | 46 | fmt.Println("Original data:") 47 | fmt.Println(storage.GetDecryptStorageData()) 48 | 49 | keyText := fmt.Sprintf(keyFmt, 1) 50 | err = storage.RemoveItem(keyText) 51 | if err != nil { 52 | fmt.Println("Error while remove key:", err) 53 | } 54 | 55 | fmt.Println("After removing:") 56 | fmt.Println(storage.GetDecryptStorageData()) 57 | 58 | sd, err := ss.LoadInfo(fileName, secret) 59 | if err != nil { 60 | fmt.Println("Error while reading:", err) 61 | } 62 | fmt.Println("The data that was read from file:", fileName) 63 | fmt.Println(sd.GetDecryptStorageData()) 64 | } 65 | 66 | // This example shows how to create a new secure storage list. 67 | // 1. Add 10 new items with the following format: key: "The key is: %v", Value: "The data is (sum of key digits): %v" 68 | // 2. Print it 69 | // 3. Save it to file 70 | // 4. Remove 1 item from it 71 | // 5. Print it again 72 | // 6. Read the saved storage 73 | // 7. Print it again 74 | func Example_storage() { 75 | storage, secret := generateSecureStorage() 76 | playWithSecureStorage(storage, secret) 77 | } 78 | -------------------------------------------------------------------------------- /defs/libsecurityCommonForTest.go: -------------------------------------------------------------------------------- 1 | // Package defs : This package is responsible for the common tests 2 | package defs 3 | 4 | import ( 5 | "io/ioutil" 6 | "testing" 7 | 8 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 9 | ss "github.com/ibm-security-innovation/libsecurity-go/storage" 10 | ) 11 | 12 | // StoreLoadTest : common store load testing for all the different properties 13 | func StoreLoadTest(t *testing.T, userData interface{}, propertyName string) { 14 | filePath := "./tmp.txt" 15 | key := "key" 16 | secret := []byte("12345678") 17 | logger.Init(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard) 18 | 19 | storage, err := ss.NewStorage(secret, false) 20 | if err != nil { 21 | t.Errorf("Fatal error: can't create storage, error: %v", err) 22 | } 23 | s := Serializers[propertyName] 24 | err = s.AddToStorage(key, userData, storage) 25 | if err != nil { 26 | t.Fatal("Fatal error: can't add to storage, error:", err) 27 | } 28 | err = s.AddToStorage(key, nil, storage) 29 | if err == nil { 30 | t.Errorf("Test fail: Successfully add undefined property to storage") 31 | } 32 | err = s.AddToStorage(key, userData, nil) 33 | if err == nil { 34 | t.Errorf("Test fail: Successfully add property to nil storage") 35 | } 36 | storage.StoreInfo(filePath) 37 | storage, err = ss.LoadInfo(filePath, secret) 38 | if err != nil { 39 | t.Fatal("Fatal error: can't load from storage, error:", err) 40 | } 41 | _, err = s.ReadFromStorage(key, nil) 42 | loadStorage := storage.GetDecryptStorageData() 43 | 44 | if err == nil { 45 | t.Fatal("Fatal error: Read pass but storage is nil") 46 | } 47 | _, err = s.ReadFromStorage("", loadStorage) 48 | if err == nil { 49 | t.Fatal("Fatal error: Read pass but the key is empty") 50 | } 51 | _, err = s.ReadFromStorage(key, loadStorage) 52 | if err != nil { 53 | t.Fatal("Fatal error: can't load from storage, error:", err) 54 | } 55 | data, err := s.ReadFromStorage(key, loadStorage) 56 | if err != nil { 57 | t.Fatal("Fatal error: can't read from storage, error:", err) 58 | } 59 | if s.IsEqualProperties(userData, data) == false { 60 | t.Fatal("Fatal error: Data read from storage:", s.PrintProperties(data), "is not equal to the one that was write to storage:", userData) 61 | } 62 | if s.IsEqualProperties(userData, "") == true { 63 | t.Fatal("Fatal error: unequal properies were found equal") 64 | } 65 | logger.Trace.Println("Data:", s.PrintProperties(data)) 66 | } 67 | -------------------------------------------------------------------------------- /restful/libsecurity-restful/libsecurity_command.go: -------------------------------------------------------------------------------- 1 | package libsecurityRestful 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/emicklei/go-restful" 8 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 9 | ) 10 | 11 | const ( 12 | handleFileCommand = iota 13 | handleCommand 14 | ) 15 | 16 | var ( 17 | // Version : the libsecurity version 18 | Version = "2.2" 19 | // MinVersion : the libsecurity sub version 20 | MinVersion = ".0008" 21 | 22 | urlCommands = make(cr.CommandToPath) 23 | 24 | commandsToPath = []cr.ComamndsToPath{ 25 | {handleFileCommand, "%v"}, 26 | {handleCommand, "%v"}, 27 | } 28 | 29 | lock sync.Mutex 30 | ) 31 | 32 | // GetVersion : return the libsecurity version as a string 33 | func GetVersion() string { 34 | return fmt.Sprintf("%s:%s%s", "Security Tool version", Version, MinVersion) 35 | } 36 | 37 | func initCommandToPath() { 38 | for _, c := range commandsToPath { 39 | urlCommands[c.Command] = c.Path 40 | } 41 | } 42 | 43 | func (s LibsecurityRestful) loadStroreRoute(service *restful.WebService) { 44 | str := fmt.Sprintf(urlCommands[handleFileCommand], storePath) 45 | service.Route(service.PUT(str). 46 | Filter(s.SuperUserFilter). 47 | To(s.restStoreData). 48 | Doc("Store Security Tool data to file"). 49 | Operation("updateLibsecurityDataFile"). 50 | Reads(cr.SecureFile{}). 51 | Writes(cr.StringMessage{})) 52 | 53 | str = fmt.Sprintf(urlCommands[handleFileCommand], loadPath) 54 | service.Route(service.PATCH(str). 55 | Filter(s.SuperUserFilter). 56 | To(s.restLoadData). 57 | Doc("Read Security Tool data from file"). 58 | Operation("loadLibsecurityDataFile"). 59 | Reads(cr.SecureFile{})) 60 | } 61 | 62 | func (s LibsecurityRestful) versionRoute(service *restful.WebService) { 63 | str := fmt.Sprintf(urlCommands[handleCommand], cr.VersionPath) 64 | service.Route(service.GET(str). 65 | // Filter(s.SuperUserFilter). 66 | To(s.restGetVersion). 67 | Doc("Get Security Tool version"). 68 | Operation("getLibsecurityVersion"). 69 | Writes(cr.StringMessage{})) 70 | } 71 | 72 | // RegisterBasic : register the libsecurity to the RESTFul API container 73 | func (s LibsecurityRestful) RegisterBasic(container *restful.Container) { 74 | servicePath = cr.ServicePathPrefix + cr.Version + stPrefix 75 | service := new(restful.WebService) 76 | service. 77 | Path(servicePath). 78 | Consumes(restful.MIME_JSON). 79 | Produces(restful.MIME_JSON). 80 | Doc("The Security Tool") 81 | 82 | s.loadStroreRoute(service) 83 | s.versionRoute(service) 84 | container.Add(service) 85 | } 86 | -------------------------------------------------------------------------------- /acl/aclEntry.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 8 | ) 9 | 10 | // PermissionsMap : hash to check if a premission was defined 11 | type PermissionsMap map[en.Permission]interface{} 12 | 13 | var pLock sync.Mutex 14 | 15 | // Entry : structure that holds the entity name and the set of permissions associated to this entry 16 | type Entry struct { 17 | EntityName string 18 | Permissions PermissionsMap 19 | } 20 | 21 | func (a Entry) String() string { 22 | return fmt.Sprintf("Name: %v, permissions: %v", a.EntityName, a.Permissions) 23 | } 24 | 25 | func isPermissionValid(permission en.Permission) error { 26 | if len(permission) == 0 { 27 | return fmt.Errorf("Permission is not valid: Length must be larger than 0") 28 | } 29 | return nil 30 | } 31 | 32 | // NewEntry : Generate a new ACL entry structure 33 | func NewEntry(name string) (*Entry, error) { 34 | err := en.IsEntityNameValid(name) 35 | if err != nil { 36 | return nil, err 37 | } 38 | a := Entry{EntityName: name, Permissions: make(PermissionsMap)} 39 | return &a, nil 40 | } 41 | 42 | // AddPermission : If the permission is valid and was not set yet, add it to the entry's permission list 43 | func (a *Entry) AddPermission(permission en.Permission) (bool, error) { 44 | pLock.Lock() 45 | defer pLock.Unlock() 46 | 47 | err := isPermissionValid(permission) 48 | if err != nil { 49 | return false, err 50 | } 51 | _, exist := a.Permissions[permission] 52 | if exist { 53 | return false, fmt.Errorf("Cannot add permission: '%v', it already exists in the permission list", permission) 54 | } 55 | a.Permissions[permission] = "" 56 | return true, nil 57 | } 58 | 59 | // RemovePermission : Remove the given permission from the ACL entry 60 | func (a *Entry) RemovePermission(permission en.Permission) error { 61 | pLock.Lock() 62 | defer pLock.Unlock() 63 | 64 | err := isPermissionValid(permission) 65 | if err != nil { 66 | return err 67 | } 68 | _, exist := a.Permissions[permission] 69 | if exist == false { 70 | return fmt.Errorf("Cannot remove permission: '%v', it does not exist in the permission list", permission) 71 | } 72 | delete(a.Permissions, permission) 73 | return nil 74 | } 75 | 76 | // CheckPermission : Check if a given permission is in the entry's list 77 | func (a Entry) CheckPermission(permission en.Permission) (bool, error) { 78 | pLock.Lock() 79 | defer pLock.Unlock() 80 | 81 | err := isPermissionValid(permission) 82 | if err != nil { 83 | return false, err 84 | } 85 | _, exist := a.Permissions[permission] 86 | return exist, nil 87 | } 88 | -------------------------------------------------------------------------------- /salt/salt_examples_test.go: -------------------------------------------------------------------------------- 1 | package salt_test 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "hash" 7 | 8 | "github.com/ibm-security-innovation/libsecurity-go/salt" 9 | ) 10 | 11 | const ( 12 | minSecretLen = 1 13 | maxSecretLen = 255 14 | ) 15 | 16 | var ( 17 | BasicSecret = []byte("ABCDABCD") 18 | BasicSalt = []byte("A1B2") 19 | ) 20 | 21 | func getSaltExample(secret, sa []byte, iter int, f func() hash.Hash, size int) ([]byte, error) { 22 | mySalt, err := salt.NewSalt(secret, minSecretLen, maxSecretLen, sa) 23 | if err != nil { 24 | return nil, err 25 | } 26 | mySalt.Iterations = iter 27 | mySalt.OutputLen = size 28 | mySalt.Digest = f 29 | return mySalt.Generate(minSecretLen, maxSecretLen) 30 | } 31 | 32 | func getRandomSaltExample(secret []byte, saltLen int, iter int, f func() hash.Hash, size int) ([]byte, []byte, error) { 33 | salting, _ := salt.GetRandomSalt(saltLen) 34 | mySalt, err := salt.NewSalt(secret, minSecretLen, maxSecretLen, salting) 35 | if err != nil { 36 | return nil, nil, err 37 | } 38 | mySalt.Iterations = iter 39 | mySalt.OutputLen = size 40 | mySalt.Digest = f 41 | res, err := mySalt.Generate(minSecretLen, maxSecretLen) 42 | if err != nil { 43 | return nil, nil, err 44 | } else { 45 | return res, salting, nil 46 | } 47 | } 48 | 49 | // This example shows how to generate a saltetd password from a given password and default salt 50 | func ExampleGenerateSaltedPassword() { 51 | pass := "MyPassword" 52 | res, err := salt.GenerateSaltedPassword([]byte(pass), minSecretLen, maxSecretLen, BasicSalt, -1) 53 | if err != nil { 54 | fmt.Println("GenerateSaltedPassword failed, error:", err) 55 | } else { 56 | fmt.Printf("* Generate basic salted password from a given password: '%v', using the default parameters (sha1, show full password, 1 iteration and random salt: %v) is: %v", pass, BasicSalt, res) 57 | } 58 | } 59 | 60 | // This example shows how to generate a randomly salted secret contain 8 characters, using hash function of md5 and 3 iterations of calculations 61 | func ExampleGetRandomSalt() { 62 | iter := 3 63 | f := md5.New 64 | size := 8 65 | saltLen := 32 66 | 67 | randSalt, _ := salt.GetRandomSalt(saltLen) 68 | mySalt, err := salt.NewSalt(BasicSecret, minSecretLen, maxSecretLen, randSalt) 69 | if err != nil { 70 | fmt.Println("Error while creating the new salt structure:", err) 71 | } 72 | mySalt.Iterations = iter 73 | mySalt.OutputLen = size 74 | mySalt.Digest = f 75 | res, err := mySalt.Generate(minSecretLen, maxSecretLen) 76 | if err != nil { 77 | fmt.Println("GetRandomSaltExample failed with error:", err) 78 | } else { 79 | fmt.Println("* Salted password of secret key:", string(BasicSecret), ",random salt length:", saltLen, randSalt, 80 | "with", iter, "iterations, output password length:", size, "bytes and MD5 function is:", res) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /swagger-dist/forewind/app/v1/libsecurity: -------------------------------------------------------------------------------- 1 | { 2 | "swaggerVersion": "1.2", 3 | "apiVersion": "", 4 | "basePath": "/", 5 | "resourcePath": "/forewind/app/v1/libsecurity", 6 | "apis": [ 7 | { 8 | "path": "/forewind/app/v1/libsecurity/store", 9 | "description": "The Security Tool", 10 | "operations": [ 11 | { 12 | "type": "common_restful.StringMessage", 13 | "method": "PUT", 14 | "summary": "Store Security Tool data to file", 15 | "nickname": "updateLibsecurityDataFile", 16 | "parameters": [ 17 | { 18 | "type": "common_restful.SecureFile", 19 | "paramType": "body", 20 | "name": "body", 21 | "description": "", 22 | "required": true, 23 | "allowMultiple": false 24 | } 25 | ], 26 | "produces": [ 27 | "application/json" 28 | ], 29 | "consumes": [ 30 | "application/json" 31 | ] 32 | } 33 | ] 34 | }, 35 | { 36 | "path": "/forewind/app/v1/libsecurity/load", 37 | "description": "The Security Tool", 38 | "operations": [ 39 | { 40 | "type": "void", 41 | "method": "PATCH", 42 | "summary": "Read Security Tool data from file", 43 | "nickname": "loadLibsecurityDataFile", 44 | "parameters": [ 45 | { 46 | "type": "common_restful.SecureFile", 47 | "paramType": "body", 48 | "name": "body", 49 | "description": "", 50 | "required": true, 51 | "allowMultiple": false 52 | } 53 | ], 54 | "produces": [ 55 | "application/json" 56 | ], 57 | "consumes": [ 58 | "application/json" 59 | ] 60 | } 61 | ] 62 | }, 63 | { 64 | "path": "/forewind/app/v1/libsecurity/version", 65 | "description": "The Security Tool", 66 | "operations": [ 67 | { 68 | "type": "common_restful.StringMessage", 69 | "method": "GET", 70 | "summary": "Get Security Tool version", 71 | "nickname": "getLibsecurityVersion", 72 | "parameters": [], 73 | "produces": [ 74 | "application/json" 75 | ], 76 | "consumes": [ 77 | "application/json" 78 | ] 79 | } 80 | ] 81 | } 82 | ], 83 | "models": { 84 | "common_restful.SecureFile": { 85 | "id": "common_restful.SecureFile", 86 | "required": [ 87 | "FilePath", 88 | "Secret" 89 | ], 90 | "properties": { 91 | "FilePath": { 92 | "type": "string" 93 | }, 94 | "Secret": { 95 | "type": "string" 96 | } 97 | } 98 | }, 99 | "common_restful.StringMessage": { 100 | "id": "common_restful.StringMessage", 101 | "required": [ 102 | "Str" 103 | ], 104 | "properties": { 105 | "Str": { 106 | "type": "string" 107 | } 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /swagger-dist/key.private: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJJwIBAAKCAgEAwjDGUTPehq+jHzhPJzGv/W9w12cn8j4p4E7fU8WyCXKKlI7b 3 | L6TAnFre53mkd7Eqq1+4Rsg7lZOp7nDd2yFnyxz5QIPycc3WMc/DRgS0Q65U55LR 4 | Sln9k/T3NzSzU8nwyMXNqkSlFaLSrGb3E9zgK3HUlFUfk+gDqS3V/mFcV4jnaoTC 5 | Y2r7MRKMvNlMTVIelx/6dNTcQKqKKI3YpLVeF7Sh6KH1ppxmprrAxVmFcMSGLHEK 6 | ci9J7+mWB1fQTHOh7aJ2Kzzec7BNWNbQYIil7iE+bO0CjvpUacpGpAb8EY6PaOMe 7 | gvuw+QX3R3UciYKEm8E+fUr8cebF3S4h7Aq3ZWbh9xKL2c5vnp3BokFUKJFv2Ulp 8 | WBcvXjkVIZQifk50h4ctoS9MTlZ7Z3sxLylkHX3w/BDJzzhSoKkWnvxHhiLvVmmy 9 | bZZPsKLQU9gykpmqUgbHalvD4tfaiyDZysl5drS1TdmdDOnoCHxb8b2+07cUS1aa 10 | 0VAt1bmPagleDzjGMdyW7Sl5ZYgMsnHJaQg6m0ClEm6EbKhRBrinoUxEy3uhMBGi 11 | 7tbEBU+nEOd5Mb8qNAHzTvQbdPpBHObYEtvPNQ9PSVhx4ZvF2g66WLQP18ydL398 12 | +9sqQ4IPCdy25wRKLqMk8vqjVWGDYWgm0H/I6DLthgaYkmakgKWlZtQ0hokCAwEA 13 | AQKCAgBByfp2Kkd6g+iUvCP82FWSW/3zzAv5gtv3FWGN5Q1TLA4NFHTnqD5k+lhI 14 | PardrhURqEUrHc0p+LP6hxxyj9e1NWSwWcN6kMy/ZW73usJDp702E0R6lxBU3woe 15 | FummOmUr7++kfssbEGWlaODORudfcJofgtNG7bTHzTKRkvSQ513XdZDR0720O1+Y 16 | 31l9XJPmiTv19uODuQNDiALckmGFUHoF8VDjGFuiouSKN7EAqmP2jHmIg8Ld19ov 17 | NK3qKMa0VnUIYv/OW+1oaqZ//euRSIv4pTdXrzGIAbJ7Aa7GV/D4DPIaWCD1zPKg 18 | r+lyINjCV+7OzefGwvNH4sg1StAkoGVmmBQ8Moq7kORLNrVv5O0PNK0+2Z5mGqsm 19 | TwnDmxu3sQ+1jw2+gJMiKvWYk9+FQyA403Odqdp7MxY5mo3L/shxT+65kUTBQRsr 20 | /pInuObcl13yzB3Lb8t6ukYLqjkm1g5kQwQzq+mzcTzFRD9uNTTZekZE7hKM5Jw+ 21 | dmylefVL2pK/0wL48H9BqgfcoeAxq3banE0JphAKxLom2n/XigaOMjNSfYfkfAf4 22 | sb0jFuPUP6xHJar5zzBwuXGu4jl54BVbUVhDDXIDJU2fxHnrj8rVRd3Y4IY/UccZ 23 | UbSMivyf0sFBCRRljLU7yVu0/t0J+ZGgfH0OeopXLdS5tYBMgQKCAQEA25Ejl/vd 24 | 4jQKOpZirbfV7Rgx7OdHLvJhjaXCS5g/mW6UxwDAnpEWAt6LJZ0ZhhMDvfUc4evW 25 | igz6mTTe0ld1fBKmv3NxmHpXoZUE3jbIVIXTlqyXj5RMZJ3/O29UuBIHMJlG+MfQ 26 | BcsaoGkuqnzMfZiDJRCD0Heg5Iv1hHelHzrIC0TLYahu2xxmdcJ29snbLoZaAUxa 27 | hEaUqVlzSNeZWUCnKLrNlcTHw0cXCKt3deyNT5f5fmfrtsM/4RSPsFesaNUcclQj 28 | OHzgb0Fgd+VYYdqfHkEAXQsGcljBuJo7AZWjKgW7+DBZegpJ2c7VD5mhVZB0W2s7 29 | TcygvBx6AxMO0QKCAQEA4mmvULL2e7N0nXdSZ696iwL27sico/dlMCdVS9Y1YX8S 30 | qw8uLSS5cnWpZ8OR/q3eg1SkQdQrazNWpggjMoLtcbE6hxc5JYcByy+O7Ljspt0m 31 | +v0V8ooOmOIB0OgaodeWRBHC81JaawJn9ll3F61pw43mNGEpU03lJYaVruQWJ5RF 32 | QfM/SdqItXINo3t+k031QzJyhMRtAeGM+TEbIZ2TPcJ6uB4SF/sCPTF9iNClkQEb 33 | DSkoTxvsTeCVgOBGoIDoEN+H37LnAuTLvdVnlyErZsQeqcSaoATKYkvh0Jcj82FH 34 | DMYdGQ3wiNB+ux7S8y0iVKs0/sNeXZZIq+5qzWUaOQKCAQAXQ6mh0O4RqbHrw0Nx 35 | ZKkmFgo0L807DDOJ7qZHnDog57ab31iJdJYAd9QGiD7MFfeviOkmhvaUagujcBar 36 | 8thf7Rn97x16Knxegouhf+x6CDQDIwZsECqH/rWsdn9jgYWBgLwkYAeKlZqcpIht 37 | SAviwuYiZz2QORyzGqFJB+RXCsNnbkKedlpInRm+IHy6aWjv5QxkVtLAcToPN4sf 38 | jLiS3GbZyfE+jTsdVcr7ADAILqhKZq/o1NoE42tBTgxrgt6hvPLmqkORGKovH545 39 | FfPnamKxsfF36lMkioL8LFtUL6a6LSFdisHwTGbGzUOGA6E0j7nEdfgyphvh0qKa 40 | 8MoBAoIBAB0KXThGnCLls7F22pT+BwPJj461IJJPayUwBlrB0Pr+Z5CVU+Dqcvbh 41 | bKcoiJNYDdBejOr8oc0QOq71ID6cWIRarR0it2u+01YSdYybc487koJi7+u6FYE6 42 | i+0VuETF+Dom5RB8oahUGK2acBITZht1Ge/h/oD23stfZdeW3diZxgBdiOKaKyrK 43 | xEER1/xg5XKHB/VLlIfHPGPCDq7THU2wojj3cm6s/W6H6Igh6vqcX7Hic48iD781 44 | bTeojKaNojZ31V+1/Ld8TT2+H5lc4co+oYg49XRM6QCOpxowKKZQ9eIHjzfld9K0 45 | m2y42WnjgiHGC/gZawMqAYw7+mr56tECggEAMQmMjing1/0dpIeVOQbAUuLAkvDt 46 | /IREJzD/30k5a+Fa5ihLFjrmHIGKEGwRaD8VKkCVQN7Go3ukgainTIjIj1gHqRAL 47 | uaqWM0OO6Wq0oM32cwrgBPRIPKRrKLtWCKH2stSRYU+itRhurUGmcgWc2q6HWsC5 48 | ew//9eE/4MqIdFcgA+MLR+1WmkMB/t5zahrD5sxTjRqvy1NBmCTQtgXxdE8zho6y 49 | ibKL04xId+vjbqlUaPocmMLMocHzH3fzfgoSbBGrf/g0O3CDI12Ei1gWjQqqDGyY 50 | 0hnWCJi47YYq0yuDuUMolk+lWzTK9yJlG8Bnbowp2NXKQBK2646O7EoyZw== 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /app/token/token_test.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | // "fmt" 5 | "testing" 6 | 7 | am "github.com/ibm-security-innovation/libsecurity-go/accounts" 8 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 9 | ) 10 | 11 | const ( 12 | defaultUserName = "User1" 13 | defaultIP = "1.2.3.4" 14 | 15 | privateKeyFilePath = "./dist/key.private" 16 | ) 17 | 18 | var () 19 | 20 | func init() { 21 | } 22 | 23 | // Verify that only the same tokens are equal 24 | func Test_GenerateToken(t *testing.T) { 25 | usersName := []string{defaultUserName, defaultUserName + "a", ""} 26 | ipsStr := []string{defaultIP, defaultIP + "1", ""} 27 | usersPrivilege := am.GetUsersPrivilege() 28 | updatePassword := []bool{true, false} 29 | signKey, verifyKey := SetupAToken(privateKeyFilePath) 30 | 31 | for _, name := range usersName { 32 | for _, ip := range ipsStr { 33 | for p := range usersPrivilege { 34 | for _, u := range updatePassword { 35 | token1, _ := GenerateToken(name, p, u, ip, signKey) 36 | data, err := ParseToken(token1, ip, verifyKey) 37 | if err != nil || data.UserName != name || data.Privilege != p || data.UpdatePassword != u { 38 | t.Errorf("Test fail: the parsed token != generated token, error: %v", err) 39 | } 40 | _, err = ParseToken(token1, ip+"1", verifyKey) 41 | if err == nil { 42 | t.Errorf("Test fail: return successful from parsed token but the IPs are different") 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | // Verify that only user with the relevant privilege can run the operation 51 | // Verify that the IsTheSameUser return the expected value 52 | func Test_CheckPrivilegeAndSameUser(t *testing.T) { 53 | groupsName := []string{defs.UsersGroupName, defs.AdminGroupName, defs.SuperUserGroupName} 54 | usersName := []string{"user", "admin", "super"} 55 | permissions := []string{am.UserPermission, am.AdminPermission, am.SuperUserPermission} 56 | 57 | signKey, verifyKey := SetupAToken(privateKeyFilePath) 58 | 59 | for _, name := range usersName { 60 | usersList.AddUser(name) 61 | } 62 | for i, name := range groupsName { 63 | usersList.AddGroup(name) // to be on the safe side 64 | for j := i; j < len(usersName); j++ { 65 | usersList.AddUserToGroup(name, usersName[j]) 66 | } 67 | } 68 | for i, name := range usersName { 69 | token1, _ := GenerateToken(name, permissions[i], true, defaultIP, signKey) 70 | for j := range usersName { 71 | ok, err := IsPrivilegeOk(token1, permissions[j], defaultIP, verifyKey) 72 | if j > i && ok == true { 73 | t.Errorf("Test fail: Is privilege returns %v but the token privilege is '%v' and the check was for '%v', error %v", ok, usersName[i], permissions[j], err) 74 | } else if j <= i && ok == false { 75 | t.Errorf("Test fail: Is privilege returns %v but the token privilege is '%v' and the check was for '%v', error %v", ok, usersName[i], permissions[j], err) 76 | } 77 | } 78 | } 79 | for _, name1 := range usersName { 80 | token1, _ := GenerateToken(name1, name1, true, defaultIP, signKey) 81 | for _, name2 := range usersName { 82 | ok, _ := IsItTheSameUser(token1, name2, defaultIP, verifyKey) 83 | if name1 != name2 && ok == true { 84 | t.Errorf("Test fail: IsItTheSameUser returns true but the names are different: '%v', '%v'", name1, name2) 85 | } else if name1 == name2 && ok == false { 86 | t.Errorf("Test fail: IsItTheSameUser returns false but the names are the same: '%v', '%v'", name1, name2) 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /acl/acl_example_test.go: -------------------------------------------------------------------------------- 1 | package acl_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ibm-security-innovation/libsecurity-go/acl" 7 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 8 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 9 | ) 10 | 11 | const ( 12 | userName1 = "User1" 13 | userName2 = "User2" 14 | resourceName = "Camera" 15 | userInGroupName1 = "gUser1" 16 | userInGroupName2 = userName2 17 | groupName = "support" 18 | canUsePermission = "Can use" 19 | allPermission = "All can use it" 20 | usersPermission = "for users only" 21 | supportPermission = "Can take" 22 | unsetPermission = "This permission is not allowed" 23 | ) 24 | 25 | var ( 26 | usersName = []string{userName1, userName2} 27 | groupUsersName = []string{userInGroupName1, userInGroupName2} 28 | ) 29 | 30 | func initEntityManager() *en.EntityManager { 31 | entityManager := en.New() 32 | for _, name := range usersName { 33 | entityManager.AddUser(name) 34 | } 35 | entityManager.AddGroup(groupName) 36 | for _, name := range groupUsersName { 37 | entityManager.AddUser(name) 38 | entityManager.AddUserToGroup(groupName, name) 39 | } 40 | entityManager.AddResource(resourceName) 41 | a := acl.NewACL() 42 | entityManager.AddPropertyToEntity(resourceName, defs.AclPropertyName, a) 43 | return entityManager 44 | } 45 | 46 | // Shows how to add/check/remove permissions for a n entity (resource) of a user or a group entity 47 | func Example_acl() { 48 | entityManager := initEntityManager() 49 | fmt.Println("ExampleShowACLAddCheckRemovePermissions") 50 | fmt.Printf("User %q permission %q is %v\n", userName1, canUsePermission, 51 | acl.CheckUserPermission(entityManager, userName1, resourceName, en.Permission(canUsePermission))) 52 | data, _ := entityManager.GetPropertyAttachedToEntity(resourceName, defs.AclPropertyName) 53 | a, ok := data.(*acl.Acl) 54 | if ok == false { 55 | fmt.Println("Error: Cannot get property", defs.AclPropertyName, "attached to resource", resourceName) 56 | return 57 | } 58 | a.AddPermissionToEntity(entityManager, userName1, en.Permission(canUsePermission)) 59 | fmt.Printf("User %q permission %q is: %v\n", userName1, canUsePermission, 60 | acl.CheckUserPermission(entityManager, userName1, resourceName, en.Permission(canUsePermission))) 61 | a.AddPermissionToEntity(entityManager, groupName, en.Permission(supportPermission)) 62 | a.AddPermissionToEntity(entityManager, groupName, en.Permission(canUsePermission)) 63 | a.AddPermissionToEntity(entityManager, defs.AclAllEntryName, en.Permission(allPermission)) 64 | a.AddPermissionToEntity(entityManager, userInGroupName1, en.Permission(usersPermission)) 65 | permissions, _ := acl.GetUserPermissions(entityManager, userInGroupName1, resourceName) 66 | fmt.Printf("All the permissions for user %q on resource %q are: %q\n", 67 | userInGroupName1, resourceName, permissions) 68 | permissions, _ = acl.GetUserPermissions(entityManager, groupName, resourceName) 69 | fmt.Printf("All the permissions for group %q on resource %q are: %q\n", groupName, resourceName, permissions) 70 | a.RemovePermissionFromEntity(groupName, en.Permission(canUsePermission)) 71 | fmt.Printf("After remove permission: %q from group %q\n", canUsePermission, groupName) 72 | fmt.Printf("User %q permission %q is: %v\n", userInGroupName1, canUsePermission, 73 | acl.CheckUserPermission(entityManager, userInGroupName1, resourceName, en.Permission(canUsePermission))) 74 | fmt.Printf("All the permissions are: %q\n", a.GetAllPermissions()) 75 | } 76 | -------------------------------------------------------------------------------- /password/password_example_test.go: -------------------------------------------------------------------------------- 1 | package password_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ibm-security-innovation/libsecurity-go/password" 7 | "github.com/ibm-security-innovation/libsecurity-go/salt" 8 | ) 9 | 10 | var ( 11 | minPasswordLength = 1 12 | maxPasswordLength = 255 13 | ) 14 | 15 | // Example of how to use the password. 16 | // 1. Create a new password. 17 | // 2. Verify that the initial password is set correctly 18 | // 3. Change the user's password 19 | // 4. Verify that the old password is not valid anymore 20 | // 5. Verify that the new password is valid 21 | // 6. Verify that the old password can't be used any more 22 | // (at least not as long as it remains in the old passwords list) 23 | func ExampleUserPwd() { 24 | id := "User-1" 25 | pwd := []byte("a1B2c3d^@") 26 | saltStr, _ := salt.GetRandomSalt(8) 27 | 28 | userPwd, _ := password.NewUserPwd(pwd, saltStr, true) 29 | tPwd, _ := salt.GenerateSaltedPassword(pwd, minPasswordLength, maxPasswordLength, saltStr, -1) 30 | newPwd := password.GetHashedPwd(tPwd) 31 | err := userPwd.IsPasswordMatch(newPwd) 32 | if err != nil { 33 | fmt.Println("Error", err) 34 | } 35 | userNewPwd := []byte(string(pwd) + "a") 36 | newPwd, err = userPwd.UpdatePassword(userPwd.Password, userNewPwd, true) 37 | if err != nil { 38 | fmt.Printf("Password update for user %v to new password '%v' (%v) failed, error %v\n", id, newPwd, string(userNewPwd), err) 39 | } else { 40 | fmt.Printf("User '%v', updated password to '%v' (%v)\n", id, newPwd, string(userNewPwd)) 41 | } 42 | err = userPwd.IsPasswordMatch(newPwd) 43 | if err != nil { 44 | fmt.Printf("Check of the new password, '%v' (%v), for user %v failed, error %v\n", newPwd, string(userNewPwd), id, err) 45 | } else { 46 | fmt.Printf("User '%v', new password '%v' (%v) verified successfully\n", id, newPwd, string(userNewPwd)) 47 | } 48 | err = userPwd.IsPasswordMatch(pwd) 49 | if err == nil { 50 | fmt.Printf("Error: Old password '%v' (%v) for user %v accepted\n", pwd, string(pwd), id) 51 | } else { 52 | fmt.Printf("User '%v', Note that the old password '%v' (%v) cannot be used anymore\n", id, pwd, string(pwd)) 53 | } 54 | newPwd, err = userPwd.UpdatePassword(userPwd.Password, pwd, true) 55 | if err == nil { 56 | fmt.Printf("Error: Password '%v' (typed password %v) for user %v was already used\n", newPwd, string(pwd), id) 57 | } else { 58 | fmt.Printf("Entity '%v'. Note that the old password (entered password) %v was already used\n", id, string(pwd)) 59 | } 60 | } 61 | 62 | // Example of how to use the reset password function: 63 | // This function resets the current password, 64 | // selects a new password with short expiration time 65 | // and lets the user use it exactly once 66 | func ExampleUserPwd_ResetPassword() { 67 | id := "User1" 68 | pwd := []byte("a1b2C@3d4") 69 | 70 | saltStr, _ := salt.GetRandomSalt(10) 71 | userPwd, _ := password.NewUserPwd(pwd, saltStr, false) 72 | tmpPwd, _ := userPwd.ResetPassword() 73 | tPwd, _ := salt.GenerateSaltedPassword(tmpPwd, 1, 100, saltStr, -1) 74 | newPwd := password.GetHashedPwd(tPwd) 75 | err := userPwd.IsPasswordMatch(newPwd) 76 | if err != nil { 77 | fmt.Printf("Check of newly generated password '%v' for user %v failed, error %v\n", newPwd, id, err) 78 | } else { 79 | fmt.Printf("Entity %v, after resetting password '%v' verified successfully\n", id, newPwd) 80 | } 81 | err = userPwd.IsPasswordMatch(newPwd) 82 | if err == nil { 83 | fmt.Printf("Error: Newly generated password '%v' could be used only once\n", newPwd) 84 | } else { 85 | fmt.Printf("Newly generated password '%v', for entity %v, can only be used once\n", newPwd, id) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /otp/otpTotp_test.go: -------------------------------------------------------------------------------- 1 | package otp 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func testGetTotp(t *testing.T) *Totp { 9 | totp, err := NewTotp(BaseSecret) 10 | if err != nil { 11 | t.Error("Test fail,", err) 12 | t.FailNow() 13 | } 14 | return totp 15 | } 16 | 17 | // Verify that an error is generated only for an illegal lifespan 18 | func Test_TotpIllegalInterval(t *testing.T) { 19 | totp := testGetTotp(t) 20 | 21 | for i := 0; i < maxIntervalSec+2; i++ { 22 | totp.Interval = time.Second * time.Duration(i) 23 | _, err := totp.Now() 24 | 25 | if err == nil && (i < minIntervalSec || i > maxIntervalSec) { // The next error does not make sense to me: 26 | t.Errorf("Totp test failed: initialization was done for illegal interval duration %v, valid range should be (%vs-%vs)", 27 | time.Second*time.Duration(i), minIntervalSec, maxIntervalSec) 28 | } else if err != nil && (i >= minIntervalSec && i <= maxIntervalSec) { 29 | t.Errorf("Totp test failed: Otp init fail for legal interval duration %vs, error: %v", i, err) 30 | } 31 | } 32 | } 33 | 34 | // Verify that Different parameters (secret keys, digits, digests) result with different generated OTPs 35 | func Test_OtpOTPParamesChanged(t *testing.T) { 36 | res := make(map[string]TotpRun) 37 | for _, data := range referenceRunsTotp { 38 | if _, exists := res[data.result]; exists { 39 | t.Error("Runs with different parameters but returns the same otp: ", res[data.result], data) 40 | } else { 41 | t.Log("New OTP data:", data, "was not found in structure, so add it") 42 | res[data.result] = data 43 | } 44 | } 45 | } 46 | 47 | func setStateTotp(ot *Totp, state TotpRun) { 48 | ot.BaseOtp.Secret = state.totp.BaseOtp.Secret 49 | ot.BaseOtp.digest = state.totp.BaseOtp.digest 50 | ot.BaseOtp.Digits = state.totp.BaseOtp.Digits 51 | ot.Interval = time.Second * state.totp.Interval 52 | } 53 | 54 | // Verify that increments within the defined lifespan of a TOTP result with identical OTPs and 55 | // time increments larger than the defined lifespan of a TOTP result with different OTPs 56 | func Test_OtpTestTotp(t *testing.T) { 57 | totp := testGetTotp(t) 58 | 59 | intervalCalc := interval[0] 60 | timeOffset := []time.Duration{0 * time.Second, intervalCalc / 2, intervalCalc + (1 * time.Second)} 61 | 62 | for _, offset := range timeOffset { 63 | for _, data := range referenceRunsTotp { 64 | setStateTotp(totp, data) 65 | val, err := data.totp.AtTime(data.at.Add(offset)) 66 | if err != nil { 67 | t.Error("Test fail before running, illigal parameters:", err) 68 | continue 69 | } 70 | if offset < data.totp.Interval { 71 | if val != data.result { 72 | t.Error("OTP unexpectedly changed during defined lifespan :", data, "original run time -", data.at, 73 | "curent run time -", data.at.Add(offset), " time offset -", offset, 74 | "defined lifespan -", data.totp.Interval, 75 | ", original OTP:", data.result, "is different from the resultant OTP -", val) 76 | } else { 77 | t.Log("OTP value during defined lifespan", offset, "was not changed, as expected") 78 | } 79 | } 80 | //the == is risky: for race conditions 81 | if offset >= data.totp.Interval { 82 | if val == data.result { 83 | t.Error("OTP value did not expire when lifespan expired:", data, 84 | "original run time -", data.at, ", current run time", data.at.Add(offset), 85 | ", time offset -", offset, ", defined lifespan -", data.totp.Interval, 86 | "but the OTP calculated value was the same -", val) 87 | } 88 | } else { 89 | t.Log("OTP value out of defined lifespan", offset, "was changed, as expected") 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /restful/password-restful/pwd_command.go: -------------------------------------------------------------------------------- 1 | package passwordRestful 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/emicklei/go-restful" 7 | "github.com/ibm-security-innovation/libsecurity-go/password" 8 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 9 | ) 10 | 11 | const ( 12 | handleUserCommand = iota 13 | verifyUserPasswordCommand 14 | resetUserPasswordCommand 15 | ) 16 | 17 | var ( 18 | commandsToPath = []cr.ComamndsToPath{ 19 | {handleUserCommand, "%v/{%v}"}, 20 | {verifyUserPasswordCommand, "%v/{%v}"}, 21 | {resetUserPasswordCommand, "%v/{%v}/%v"}, 22 | } 23 | urlCommands = make(cr.CommandToPath) 24 | ) 25 | 26 | func initCommandToPath() { 27 | for _, c := range commandsToPath { 28 | urlCommands[c.Command] = c.Path 29 | } 30 | } 31 | 32 | func (p PwdRestful) setRoute(service *restful.WebService) { 33 | str := fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 34 | service.Route(service.PUT(str). 35 | Filter(p.st.SuperUserFilter). 36 | To(p.restAddPwd). 37 | Doc("Add Password"). 38 | Operation("AddPwd"). 39 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 40 | Reads(secretData{}). 41 | Writes(cr.URL{})) 42 | 43 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 44 | service.Route(service.GET(str).Filter(p.st.SameUserFilter). 45 | To(p.restGetPwd). 46 | Doc("Get Password"). 47 | Operation("getPwd"). 48 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 49 | Writes(password.UserPwd{})) 50 | 51 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 52 | service.Route(service.DELETE(str). 53 | Filter(p.st.SuperUserFilter). 54 | To(p.restDeletePwd). 55 | Doc("Remove Password"). 56 | Operation("deletePwd"). 57 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string"))) 58 | 59 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 60 | service.Route(service.PATCH(str). 61 | Filter(p.st.SameUserFilter). 62 | To(p.restUpdatePassword). 63 | Doc("Update Password"). 64 | Operation("updatePassword"). 65 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 66 | Reads(cr.UpdateSecret{}). 67 | Writes(cr.URL{})) 68 | 69 | str = fmt.Sprintf(urlCommands[verifyUserPasswordCommand], usersPath, userIDParam) 70 | service.Route(service.POST(str). 71 | Filter(p.st.SameUserFilter). 72 | To(p.restVerifyPassword). 73 | Doc("Verify that a given password is as expected"). 74 | Operation("verifyPassword"). 75 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 76 | Reads(secretData{}). 77 | Writes(cr.Match{})) 78 | 79 | str = fmt.Sprintf(urlCommands[resetUserPasswordCommand], usersPath, userIDParam, resetUserPwdPath) 80 | service.Route(service.POST(str). 81 | Filter(p.st.SuperUserFilter). 82 | To(p.restResetPassword). 83 | Doc("Reset password"). 84 | Operation("resetPassword"). 85 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 86 | Writes(secretData{})) 87 | } 88 | 89 | // RegisterBasic : register the Password to the RESTFul API container 90 | func (p PwdRestful) RegisterBasic(container *restful.Container) { 91 | servicePath = cr.ServicePathPrefix + cr.Version + pwdPrefix 92 | 93 | service := new(restful.WebService) 94 | service. 95 | Path(servicePath). 96 | Consumes(restful.MIME_JSON). 97 | Produces(restful.MIME_JSON) 98 | //.Doc("Password Management") 99 | 100 | p.setRoute(service) 101 | container.Add(service) 102 | } 103 | -------------------------------------------------------------------------------- /otp/otp_examples_test.go: -------------------------------------------------------------------------------- 1 | package otp_test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 8 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 9 | "github.com/ibm-security-innovation/libsecurity-go/otp" 10 | ) 11 | 12 | const ( 13 | entityName = "Entity1" 14 | ) 15 | 16 | var ( 17 | entityManager *en.EntityManager 18 | secret = []byte("AaB1@CDABCD12341234") 19 | ) 20 | 21 | func init() { 22 | entityManager = en.New() 23 | } 24 | 25 | func addOtpUser(id string, secret []byte, startCounter int64) { 26 | entityManager.AddUser(id) 27 | otpUser, _ := otp.NewOtpUser(secret, true, false, 10, 1, 100, 2, 0, startCounter) 28 | entityManager.AddPropertyToEntity(id, defs.OtpPropertyName, otpUser) 29 | } 30 | 31 | // The TOTP example demonstrates the following secenario: 32 | // Verify that the calculated TOTP for the current time 33 | // is as expected by the provider 34 | func ExampleTotp() { 35 | addOtpUser(entityName, secret, 0) 36 | 37 | totp, err := otp.NewTotp(secret) 38 | if err != nil { 39 | fmt.Println("TOTP cannot be initialized, Error: ", err) 40 | } 41 | code, err := totp.Now() 42 | if err != nil { 43 | fmt.Println("Cannot generate TOTP, error: ", err) 44 | } else { 45 | e, _ := entityManager.GetPropertyAttachedToEntity(entityName, defs.OtpPropertyName) 46 | if e == nil { 47 | return 48 | } 49 | entity, ok := e.(*otp.UserInfoOtp) 50 | if ok == true { 51 | ok, err := entity.VerifyOtpUserCode(code, otp.TotpType) 52 | if ok { 53 | fmt.Println("TOTP: Entity:", entityName, "Code:", code, "as expected") 54 | } else { 55 | fmt.Println("TOTP: Entity:", entityName, "Code:", code, "is not as expected, error:", err) 56 | } 57 | } 58 | } 59 | } 60 | 61 | type HotpUser struct { 62 | name string 63 | counter int64 64 | hotp *otp.Hotp 65 | } 66 | 67 | func (u HotpUser) String() string { 68 | ret := fmt.Sprintf("Entity id: %v, counter %v", u.name, u.counter) 69 | return ret 70 | } 71 | 72 | func initHotpUserslist(users []HotpUser, secret []byte) { 73 | for i, user := range users { 74 | addOtpUser(user.name, secret, user.counter) 75 | hotp, err := otp.NewHotp(secret, user.counter) 76 | if err != nil { 77 | fmt.Println("HOTP cannot be initialized, error: ", err) 78 | } 79 | users[i].hotp = hotp 80 | } 81 | } 82 | 83 | // The HOTP example demonstrates the following secenarios: 84 | // Adding 2 users, each with a different initial counter and 85 | // Repeat the following 10 times: 86 | // randomly select one of the users, verify that the calculated HOTP 87 | // is as expected by the provider and then increase the internal counter 88 | // of the randomly selected user by one 89 | func ExampleHotp() { 90 | users := []HotpUser{{name: "camera", counter: 2000}, {name: "cell-phone", counter: 1000}} 91 | initHotpUserslist(users, secret) 92 | 93 | for i := 0; i < 10; i++ { 94 | idx := rand.Int() % len(users) 95 | hotp := users[idx].hotp 96 | code, err := hotp.Next() 97 | if err != nil { 98 | fmt.Println("Cannot generate HOTP, error: ", err) 99 | } else { 100 | entity, _ := entityManager.GetPropertyAttachedToEntity(users[idx].name, defs.OtpPropertyName) 101 | if entity != nil { 102 | ok, err := entity.(*otp.UserInfoOtp).VerifyOtpUserCode(code, otp.HotpType) 103 | if ok { 104 | fmt.Printf("HOTP: %v, Code: %v as expected\n", users[idx], code) 105 | users[idx].counter++ 106 | } else { 107 | fmt.Printf("HOTP: %v code %v is not as expected, error: %v\n", users[idx], code, err) 108 | } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /defs/libsecurityCommon.go: -------------------------------------------------------------------------------- 1 | // Package defs : This package is responsible for the definition of all the common variables used by the libsecurity-go library 2 | package defs 3 | 4 | import ( 5 | "crypto/rand" 6 | "math/big" 7 | "time" 8 | 9 | // "io/ioutil" 10 | // "testing" 11 | // logger "github.com/ibm-security-innovation/libsecurity-go/logger" 12 | 13 | ss "github.com/ibm-security-innovation/libsecurity-go/storage" 14 | ) 15 | 16 | const ( 17 | // RootUserName : define the root user name for the system 18 | RootUserName = "root" 19 | 20 | // AclAllEntryName : Saved name for ACL, the same as in Linux 21 | AclAllEntryName = "All" 22 | 23 | // ExtraCharStr : The only extra charcters that can be used and need to be used by passwords 24 | ExtraCharStr = "@#%^&()'-_+=;:" 25 | 26 | // SuperUserGroupName : Saved name for ACL super users group 27 | SuperUserGroupName = "Super-users" 28 | // AdminGroupName : Saved name for ACL administrators group 29 | AdminGroupName = "Admin" 30 | // UsersGroupName : Saved name for ACL users group 31 | UsersGroupName = "Users" 32 | 33 | // AmPropertyName : Saved name for the account properties 34 | AmPropertyName string = "AM" 35 | // AclPropertyName : Saved name for the ACL properties 36 | AclPropertyName string = "ACL" 37 | // OtpPropertyName : Saved name for the OTP properties 38 | OtpPropertyName string = "OTP" 39 | // OcraPropertyName : Saved name for the OCRA properties 40 | OcraPropertyName string = "OCRA" 41 | // PwdPropertyName : Saved name for the Password properties 42 | PwdPropertyName string = "PWD" 43 | // UmPropertyName : Saved name for the users/groups/resources properties 44 | UmPropertyName string = "UM" 45 | 46 | // PasswordThrottlingMiliSec : throttling delay in mili seconds when password does not match or if the entity does not exist 47 | // to handle timing atacks 48 | PasswordThrottlingMiliSec = 1000 49 | // ThrottleMaxRandomMiliSec : maximum random throttling delay in mili seconds to be added to PasswordThrottlingMiliSec in order to handle timing atacks 50 | ThrottleMaxRandomMiliSec = 10 // to avoid timimg attacks 51 | ) 52 | 53 | var ( 54 | // PropertiesName : which properties to store/load from secure storage 55 | PropertiesName = map[string]bool{ 56 | AmPropertyName: true, 57 | AclPropertyName: true, 58 | OtpPropertyName: true, 59 | OcraPropertyName: true, 60 | PwdPropertyName: true, 61 | UmPropertyName: true, 62 | } 63 | ) 64 | 65 | // Serializer : virtual set of functions that must be implemented by each module 66 | type Serializer interface { 67 | PrintProperties(data interface{}) string 68 | AddToStorage(prefix string, data interface{}, storage *ss.SecureStorage) error 69 | ReadFromStorage(prefix string, storage *ss.SecureStorage) (interface{}, error) 70 | IsEqualProperties(d1 interface{}, d2 interface{}) bool 71 | } 72 | 73 | // SerializersMap : hash structure, the key is the module property name 74 | type SerializersMap map[string]Serializer 75 | 76 | // Serializers : create the SerializersMap structure 77 | var Serializers = make(SerializersMap) 78 | 79 | // TimingAttackSleep : sleep for given delay in mili seconds plus random delay in the given rabge: to handle timing attacks 80 | func TimingAttackSleep(baseSleepMiliSec int64, maxRandomMiliSec int64) { 81 | if maxRandomMiliSec <= 0 { 82 | maxRandomMiliSec = 1 83 | } 84 | nBig, err := rand.Int(rand.Reader, big.NewInt(maxRandomMiliSec)) 85 | if err != nil { 86 | nBig = big.NewInt(maxRandomMiliSec / 2) 87 | } 88 | time.Sleep(time.Duration(nBig.Int64()+baseSleepMiliSec) * time.Millisecond) 89 | } 90 | 91 | // GetBeginningOfTime : return 1/1/1970 92 | func GetBeginningOfTime() time.Time { 93 | return time.Date(1970, time.January, 1, 1, 0, 0, 0, time.Local) 94 | } 95 | -------------------------------------------------------------------------------- /otp/otpExternal_test.go: -------------------------------------------------------------------------------- 1 | package otp 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/base32" 6 | "strings" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | // Base32 as defined in RFC 4648 Base 32 alphabet 12 | // Convert a given string to Byte32 (upper case, unknown chars + 0,1,l will be converted to 'O') 13 | // The length of the string should be a multiple of 40 bites on base32 14 | // Note that the values for ilegal characters are not defined so I used "O" 15 | func ConvertToLegalBase32(str string, t *testing.T) string { 16 | base32chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" 17 | pad := "O" 18 | orgLen := len(str) 19 | 20 | length := ((len(str)-1)/8 + 1) * 8 21 | t.Log("Original string:", str, ", length:", len(str), ", the new base32 len", length) 22 | for i := len(str); i < length; i++ { 23 | str = pad + str 24 | } 25 | ret := []byte(strings.ToUpper(str)) 26 | for i := length - orgLen; i < len(ret); i++ { 27 | if strings.IndexByte(base32chars, ret[i]) == -1 { 28 | ret[i] = 'O' 29 | } 30 | } 31 | t.Log("Converted to base32 string", string(ret)) 32 | return string(ret) 33 | } 34 | 35 | // Check the base32 conversion 36 | func Test_ChangedToLegalBase32(t *testing.T) { 37 | secrets := [][]string{{"abc", "OOOOOABC"}, {"a0123", "OOOAOO23"}, 38 | {"aBn:{}()laA", "OOOOOABNOOOOOLAA"}} 39 | for _, str := range secrets { 40 | res := ConvertToLegalBase32(str[0], t) 41 | if res != str[1] { 42 | t.Error("Base32 test: Conversion of the string", str[0], "is not as expected (", str[1], ") received: ", res) 43 | } else { 44 | t.Log("Conversion of string (", str[1], ") to (", res, ") is as expected") 45 | } 46 | } 47 | } 48 | 49 | // Verify that an error is generated only for illegal secret key length 50 | func Test_OtpIllegalSecret(t *testing.T) { 51 | secret := make([]byte, maxSecretLen+10) 52 | 53 | for i := 0; i < maxSecretLen+10; i++ { 54 | _, err := NewOtp(secret[:i]) 55 | if err == nil && (i < minSecretLen || i > maxSecretLen) { 56 | t.Errorf("Test failed: initialization was done for a secret key with an illegal length %d. Valid lengths are in the range %d-%d", i, minSecretLen, maxSecretLen) 57 | } else if err != nil && (i >= minSecretLen && i <= maxSecretLen) { 58 | t.Errorf("Test failed: Otp initialization failed for a secret key with a legal length %d, error: %v", i, err) 59 | } 60 | } 61 | } 62 | 63 | // Test sha1 6 digits with varius secret keys against the Google authenticator calculations 64 | // The input string is converted into a valid base32 string (without undefined characters and with the appropriate length) 65 | func Test_OtpTestTotpVsGoogleAuthenticator(t *testing.T) { 66 | var val, ref, secret string 67 | b32secret := [][]string{{"JBSWY3DPEHPK3PXP", "JBSWY3DPEHPK3PXP"}, 68 | {"ABCD2345", "ABCD2345"}, {"a123", "OOOOAO23"}, 69 | {"abcA(){}AA", "OOOOOOABCAOOOOAA"}} 70 | totp, err := NewTotp(BaseSecret) 71 | if err != nil { 72 | t.Error("Test fail,", err) 73 | t.FailNow() 74 | } 75 | 76 | totp.BaseOtp.digest = sha1.New 77 | totp.BaseOtp.Digits = 6 78 | totp.Interval = time.Second * 30 79 | 80 | for _, s := range b32secret { 81 | secret = ConvertToLegalBase32(s[0], t) 82 | if secret != s[1] { 83 | t.Error("Base32 test: Conversion of the string", s[0], "is not as expected (", s[1], ") received: ", secret) 84 | continue 85 | } 86 | totp.BaseOtp.Secret, _ = base32.StdEncoding.DecodeString(secret) 87 | val, err = totp.Now() 88 | if err != nil { 89 | t.Error("Test fail before running, illigal parameters:", err) 90 | continue 91 | } 92 | ref = jsOtp(secret, (time.Now().UnixNano()+500000000.0)/1000000000.0) 93 | if val != ref { 94 | t.Error("Error: internal OTP value:", val, "is different from the external OTP value :", ref, 95 | "for the same data: sha1, #of digits", totp.BaseOtp.Digits, "secret", secret) 96 | } else { 97 | t.Log("The same internal and external OTP calculation using secret:", secret) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /acl/aclEntry_test.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "testing" 5 | 6 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 7 | ) 8 | 9 | const ( 10 | PerRead = "Read" 11 | PerWrite = "Write" 12 | PerExe = "Execute" 13 | PerTake = "Take" 14 | PerAll = "Can be used by All" 15 | PerNotAnEntry1 = "NotAnEntry1" 16 | PerNotAnEntry2 = "NotAnEntry2" 17 | 18 | entryName = "test1" 19 | ) 20 | 21 | var permissionsVec = []en.Permission{PerRead, PerWrite, PerExe, PerTake, PerAll} 22 | var notPermissionsVec = []en.Permission{PerNotAnEntry1, "", PerNotAnEntry2} 23 | var permissionsMap = make(map[en.Permission]bool) 24 | 25 | func init() { 26 | for _, p := range permissionsVec { 27 | permissionsMap[p] = true 28 | } 29 | for _, p := range notPermissionsVec { 30 | permissionsMap[p] = false 31 | } 32 | } 33 | 34 | func addPermissions(a *Entry, permissions map[en.Permission]bool, expected bool) (bool, *en.Permission) { 35 | for p, val := range permissions { 36 | if val == true { 37 | val, err := a.AddPermission(p) 38 | if len(p) == 0 && err != nil { 39 | return !expected, &p 40 | } 41 | if val != expected { 42 | return false, &p 43 | } 44 | } 45 | } 46 | return true, nil 47 | } 48 | 49 | // Verify that empty permission can't be added 50 | // verify that valid permission can be added 51 | // verify that permission that is in the list can't be added again 52 | // verify that only permission that are in the list can be removed 53 | // Verify that at the end of the loop, no permissions are left in the list 54 | func Test_AddRemovePermissions(t *testing.T) { 55 | expected := []bool{true, false} 56 | name := "User1" 57 | 58 | a, _ := NewEntry(name) 59 | // verify that empty permission can't be added 60 | p := en.Permission("") 61 | _, err := a.AddPermission(p) 62 | if err == nil { 63 | t.Errorf("Test fail: Invalid permission: '%v' was added to the %v", p, a) 64 | } 65 | // verify that valid permission can be added 66 | // verify that permission that is in the list can't be added again 67 | for _, exp := range expected { 68 | ok, permission := addPermissions(a, permissionsMap, exp) 69 | if ok == false { 70 | if exp == true { 71 | t.Errorf("Test fail: Fail to add valid permission: '%v' to %v", permission, a) 72 | } else { 73 | t.Errorf("Test fail: Permission: '%v' was added to %v but is was already added before", permission, a) 74 | } 75 | } 76 | } 77 | 78 | // verify that only permission that are in the list can be removed 79 | for p, val := range permissionsMap { 80 | err := a.RemovePermission(p) 81 | if err != nil && val == true { 82 | t.Errorf("Test fail: Fail to remove valid permission: '%v' from %v", p, a) 83 | } else if err == nil && val == false { 84 | t.Errorf("Test fail: Permission: '%v' was removed from %v, but it wasn't in the permissions list", p, a) 85 | } 86 | } 87 | if len(a.Permissions) != 0 { 88 | t.Error("Test fail: The permission list of", a, "must be empty") 89 | } 90 | } 91 | 92 | // Add permissions to an entry and verify that only permission that are in the 93 | // entry return true 94 | func Test_CheckPermissions(t *testing.T) { 95 | name := "User1" 96 | // 97 | // ul := um.NewUsersList() 98 | // user1, _ := um.NewUser(name, nil) 99 | // ul.AddUser(user1) 100 | a, _ := NewEntry(name) 101 | 102 | // verify that valid permission can be added 103 | ok, permission := addPermissions(a, permissionsMap, true) 104 | if ok == false { 105 | t.Errorf("Test fail: Fail to add valid permission: '%v' to %v", permission, a) 106 | } 107 | // verify that only permission that are in the list returns true 108 | for p, val := range permissionsMap { 109 | ok, _ := a.CheckPermission(p) 110 | if ok == false && val == true { 111 | t.Errorf("Test fail: Permission: '%v' from %v wasn't found", p, a) 112 | } else if ok == true && val == false { 113 | t.Errorf("Test fail: Permission: '%v' that is not in %v, was found", p, a) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /swagger-dist/lib/jquery.ba-bbq.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 3 | * http://benalman.com/projects/jquery-bbq-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this); -------------------------------------------------------------------------------- /restful/otp-restful/otp_command.go: -------------------------------------------------------------------------------- 1 | package otpRestful 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/emicklei/go-restful" 7 | "github.com/ibm-security-innovation/libsecurity-go/otp" 8 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 9 | ) 10 | 11 | const ( 12 | handleUserCommand = iota 13 | handleUserBlockCommand 14 | verifyUserCodeCommand 15 | ) 16 | 17 | var ( 18 | commandsToPath = []cr.ComamndsToPath{ 19 | {handleUserCommand, "%v/{%v}"}, 20 | {handleUserBlockCommand, "%v/{%v}/%v"}, 21 | {verifyUserCodeCommand, "%v/{%v}/%v"}, 22 | } 23 | 24 | urlCommands = make(cr.CommandToPath) 25 | ) 26 | 27 | func initCommandToPath() { 28 | for _, c := range commandsToPath { 29 | urlCommands[c.Command] = c.Path 30 | } 31 | } 32 | 33 | func (u OtpRestful) setRoute(service *restful.WebService) { 34 | str := fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 35 | service.Route(service.PUT(str). 36 | Filter(u.st.SuperUserFilter). 37 | To(u.restAddOtp). 38 | Doc("Add OTP"). 39 | Operation("addOtp"). 40 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 41 | Reads(cr.Secret{}). 42 | Writes(cr.URL{})) 43 | 44 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 45 | service.Route(service.GET(str). 46 | Filter(u.st.SameUserFilter). 47 | To(u.restGetOtp). 48 | Doc("Get the OTP"). 49 | Operation("getOtp"). 50 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 51 | Writes(otp.UserInfoOtp{})) 52 | 53 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 54 | service.Route(service.DELETE(str). 55 | Filter(u.st.SuperUserFilter). 56 | To(u.restDeleteOtp). 57 | Doc("Remove the OTP"). 58 | Operation("deleteOTP"). 59 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string"))) 60 | 61 | str = fmt.Sprintf(urlCommands[handleUserBlockCommand], usersPath, userIDParam, blockedStateToken) 62 | service.Route(service.GET(str). 63 | Filter(u.st.SameUserFilter). 64 | To(u.restIsOtpBlocked). 65 | Doc("Check if OTP is blocked"). 66 | Operation("isOtpBlocked"). 67 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 68 | Writes(userState{})) 69 | 70 | str = fmt.Sprintf(urlCommands[handleUserBlockCommand], usersPath, userIDParam, blockedStateToken) 71 | service.Route(service.PUT(str). 72 | Filter(u.st.SuperUserFilter). 73 | To(u.restSetOtpBlockedState). 74 | Doc("Set the OTP blocked state"). 75 | Operation("setOtpBlockedState"). 76 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 77 | Reads(userState{}). 78 | Writes(cr.URL{})) 79 | 80 | str = fmt.Sprintf(urlCommands[verifyUserCodeCommand], usersPath, userIDParam, verifyHotpTypeParam) 81 | service.Route(service.POST(str). 82 | To(u.restVerifyOtpHotpUserCode). // no filter is needed 83 | Doc("Verify that a given OTP is as expected, counter base"). 84 | Operation("verifyHotpUserCode"). 85 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 86 | Reads(cr.Secret{}). 87 | Writes(cr.Match{})) 88 | 89 | str = fmt.Sprintf(urlCommands[verifyUserCodeCommand], usersPath, userIDParam, verifyTotpTypeParam) 90 | service.Route(service.POST(str). 91 | To(u.restVerifyOtpTotpUserCode). // no filter is needed 92 | Doc("Verify that a given code is as expected, time base"). 93 | Operation("verifyTotpUserCode"). 94 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 95 | Reads(cr.Secret{}). 96 | Writes(cr.Match{})) 97 | } 98 | 99 | // RegisterBasic : register the OTP to the RESTFul API container 100 | func (u OtpRestful) RegisterBasic(container *restful.Container) { 101 | servicePath = cr.ServicePathPrefix + cr.Version + otpPrefix 102 | 103 | service := new(restful.WebService) 104 | service. 105 | Path(servicePath). 106 | Consumes(restful.MIME_JSON). 107 | Produces(restful.MIME_JSON) 108 | //.Doc("One Time Password") 109 | 110 | u.setRoute(service) 111 | container.Add(service) 112 | } 113 | -------------------------------------------------------------------------------- /restful/storage-restful/secureStorage_command.go: -------------------------------------------------------------------------------- 1 | package storageRestful 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/emicklei/go-restful" 7 | "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 8 | ) 9 | 10 | const ( 11 | handleStorageCommand = iota 12 | ) 13 | 14 | type commandToPath map[int]string 15 | 16 | var ( 17 | commandsToPath = []comamndsToPathS{ 18 | {handleStorageCommand, "%v"}, 19 | } 20 | urlCommands = make(commandToPath) 21 | // old use AmRestful = accountsRestful.NewAmRestful() 22 | ) 23 | 24 | type comamndsToPathS struct { 25 | command int 26 | path string 27 | } 28 | 29 | func initCommandToPath() { 30 | for _, c := range commandsToPath { 31 | urlCommands[c.command] = c.path 32 | } 33 | } 34 | 35 | func (s SRestful) setRoute(service *restful.WebService) { 36 | str := fmt.Sprintf(urlCommands[handleStorageCommand], storagePath) 37 | service.Route(service.PUT(str). 38 | // no filter is needed, the filter is the secure key Filter(s.st.SuperUserFilter). 39 | To(s.restCreateSecureStorage). 40 | Doc("Create a new secure storage"). 41 | Operation("CreateSecureStorage"). 42 | Param(service.HeaderParameter(secretIDParam, secretComment).DataType("string")). 43 | Writes(commonRestful.URL{})) 44 | 45 | str = fmt.Sprintf(urlCommands[handleStorageCommand], storagePath) 46 | service.Route(service.DELETE(str). 47 | // no filter is needed, the filter is the secure key Filter(s.st.SuperUserFilter). 48 | To(s.restDeleteSecureStorage). 49 | Doc("Remove the current secure storage"). 50 | Param(service.HeaderParameter(secretIDParam, secretComment).DataType("string")). 51 | Operation("RemoveSecureStorage")) 52 | 53 | str = fmt.Sprintf(urlCommands[handleStorageCommand], storagePath) 54 | service.Route(service.GET(str). 55 | // no filter is needed, the filter is the secure key Filter(s.st.SuperUserFilter). 56 | To(s.restGetSecureStorage). 57 | Doc("Get a secure storage"). 58 | Operation("getSecureStorage"). 59 | Param(service.HeaderParameter(secretIDParam, secretComment).DataType("string"))) 60 | 61 | str = fmt.Sprintf(urlCommands[handleStorageCommand], storageItemPath) 62 | service.Route(service.PATCH(str). 63 | // no filter is needed, the filter is the secure key Filter(s.st.SuperUserFilter). 64 | To(s.restAddItemToSecureStorage). 65 | Doc("Add a new item to the secure storage"). 66 | Operation("addANewItemToTheSecureStorage"). 67 | Param(service.HeaderParameter(secretIDParam, secretComment).DataType("string")). 68 | Reads(itemData{}). 69 | Writes(commonRestful.URL{})) 70 | 71 | str = fmt.Sprintf(urlCommands[handleStorageCommand], storageItemPath) 72 | service.Route(service.GET(str). 73 | // no filter is needed, the filter is the secure key Filter(s.st.SuperUserFilter). 74 | To(s.restGetItemFromSecureStorage). 75 | Doc("Get an item from the secure storage"). 76 | Operation("getAnItemFromTheSecureStorage"). 77 | Param(service.HeaderParameter(secretIDParam, secretComment).DataType("string")). 78 | Param(service.HeaderParameter(keyIDParam, keyComment).DataType("string")). 79 | Writes(itemValue{})) 80 | 81 | str = fmt.Sprintf(urlCommands[handleStorageCommand], storageItemPath) 82 | service.Route(service.DELETE(str). 83 | // no filter is needed, the filter is the secure key Filter(s.st.SuperUserFilter). 84 | To(s.restDeleteItemFromSecureStorage). 85 | Doc("Delete item from a secure storage"). 86 | Operation("deleteItemFromSecureStorage"). 87 | Param(service.HeaderParameter(secretIDParam, secretComment).DataType("string")). 88 | Param(service.HeaderParameter(keyIDParam, keyComment).DataType("string"))) 89 | } 90 | 91 | // RegisterBasic : register the Secure Storage to the RESTFul API container 92 | func (s SRestful) RegisterBasic(container *restful.Container) { 93 | servicePath = commonRestful.ServicePathPrefix + commonRestful.Version + sPrefix 94 | 95 | service := new(restful.WebService) 96 | service. 97 | Path(servicePath). 98 | Consumes(restful.MIME_JSON). 99 | Produces(restful.MIME_JSON). 100 | Doc("Secure Storage") 101 | 102 | s.setRoute(service) 103 | container.Add(service) 104 | } 105 | -------------------------------------------------------------------------------- /swagger-dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IoT API 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 83 | 84 | 85 | 86 | 94 |
 
95 |
96 |
97 | 98 | 99 | -------------------------------------------------------------------------------- /otp/otp_test.go: -------------------------------------------------------------------------------- 1 | package otp 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "crypto/sha256" 7 | "fmt" 8 | "hash" 9 | "time" 10 | ) 11 | 12 | // Following tests are required (all are implemented here): 13 | // 1. Basic functionality: 14 | // 1.1 Unit tests: 15 | // 1.1.1 Verify that empty secret keys cause an error 16 | // 1.1.2 Verify the validity of the base32 conversion function 17 | // 1.1.3 Verify that the next() function for HOTP works properly 18 | // 1.1.4 The next() and new() functions for event based OPT generate diferent OPTs 19 | // 1.2 Results accuracy 20 | // 1.2.1 Different parameters[input values?] result with different generated OTPs 21 | // 1.2.2 Repetition: Verify that identical input parameters the same parameters result with identical OTPs (TOTP and HOTP) 22 | // 1.2.3 Time manipulations: 23 | // 1.2.3.1 increments within the defined lifespan of a TOTP result with identical OTPs 24 | // 1.2.3.2 Time increments larger than the defined lifespan of a TOTP result with different OTPs 25 | // 2. External validation: 26 | // 2.1 Test that the OTP results match external code results 27 | // May need to add: 28 | // 1. System check: Client provider 29 | // 2. Resync message in the event based HMAC OTP 30 | 31 | var BaseSecret = []byte("Aa@bCDEFGH12345678") 32 | 33 | type TotpRun struct { 34 | totp *Totp 35 | at time.Time 36 | result string 37 | } 38 | 39 | func (t TotpRun) String() string { 40 | ret := fmt.Sprintf("Data: secret key = %v, digit = %v, digest = %v, interval = %v", 41 | t.totp.BaseOtp.Secret, t.totp.BaseOtp.Digits, t.totp.BaseOtp.digest, t.totp.Interval) 42 | return ret 43 | } 44 | 45 | type HotpRun struct { 46 | hotp *Hotp 47 | counter int64 48 | result string 49 | } 50 | 51 | func (t HotpRun) String() string { 52 | ret := fmt.Sprintf("Data: secret key = %v, digit = %v, digest = %v, counter = %v", 53 | t.hotp.BaseOtp.Secret, t.hotp.BaseOtp.Digits, t.hotp.BaseOtp.digest, t.hotp.Count) 54 | return ret 55 | } 56 | 57 | var hashes = []func() hash.Hash{sha1.New, sha256.New, md5.New} 58 | var secrets = []string{"ABCDABCD11112222", "AC234123456789012", "ABCDEFG12345691234"} 59 | var digits = []int{6, 7, 8} 60 | var interval = []time.Duration{30 * time.Second, 60 * time.Second} 61 | var startCounter = []int64{0, 12345} 62 | 63 | var referenceRunsTotp []TotpRun 64 | var referenceRunsHotp []HotpRun 65 | 66 | // initialize the refference structures with setup and expected results 67 | func init() { 68 | referenceRunsTotp = createReferenceRunsTotp() 69 | referenceRunsHotp = createReferenceRunsHotp() 70 | } 71 | 72 | func createReferenceRunsTotp() []TotpRun { 73 | var refRunsTotp []TotpRun 74 | at := time.Now() 75 | var useTime time.Time 76 | 77 | for _, h := range hashes { 78 | for _, s := range secrets { 79 | for _, d := range digits { 80 | for _, in := range interval { 81 | useTime = at.Round(in) 82 | totp, err := NewTotp([]byte(s)) 83 | if err != nil { 84 | fmt.Println("Error while initializing") 85 | panic(err) 86 | } 87 | totp.Interval = in 88 | totp.BaseOtp.digest = h 89 | totp.BaseOtp.Digits = d 90 | res, err := totp.AtTime(useTime) 91 | if err != nil { 92 | fmt.Println("Error while initializing") 93 | panic(err) 94 | } 95 | ref := TotpRun{totp, useTime, res} 96 | refRunsTotp = append(refRunsTotp, ref) 97 | } 98 | } 99 | } 100 | } 101 | return refRunsTotp 102 | } 103 | 104 | func createReferenceRunsHotp() []HotpRun { 105 | var refRunsHotp []HotpRun 106 | 107 | for _, h := range hashes { 108 | for _, s := range secrets { 109 | for _, d := range digits { 110 | for _, c := range startCounter { 111 | hotp, err := NewHotp([]byte(s), c) 112 | if err != nil { 113 | fmt.Println("Error while initializing") 114 | panic(err) 115 | } 116 | hotp.BaseOtp.digest = h 117 | hotp.BaseOtp.Digits = d 118 | res, err := hotp.AtCount(c) 119 | if err != nil { 120 | fmt.Println("Error while initializing") 121 | panic(err) 122 | } 123 | ref := HotpRun{hotp, c, res} 124 | refRunsHotp = append(refRunsHotp, ref) 125 | } 126 | } 127 | } 128 | } 129 | return refRunsHotp 130 | } 131 | -------------------------------------------------------------------------------- /setup/setup_storage_file.go: -------------------------------------------------------------------------------- 1 | // Initialization services. 2 | // 3 | // Utility that generates an initial secureStorage file to be used later by all other components 4 | // The usage is: 5 | // usage: generate_login_file 6 | // -generate-rsa=false: Generate RSA private/public files ('key.private', 'key.pub') 7 | // -login-file="./data.txt": First data file that includes the root user 8 | // -password="root": Root password 9 | // -secure-key="./secureKey": secure key file path 10 | package main 11 | 12 | import ( 13 | "crypto/rand" 14 | "crypto/rsa" 15 | "crypto/x509" 16 | "encoding/pem" 17 | "flag" 18 | "fmt" 19 | "io/ioutil" 20 | "log" 21 | "os" 22 | "path/filepath" 23 | 24 | am "github.com/ibm-security-innovation/libsecurity-go/accounts" 25 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 26 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 27 | "github.com/ibm-security-innovation/libsecurity-go/password" 28 | "github.com/ibm-security-innovation/libsecurity-go/salt" 29 | ss "github.com/ibm-security-innovation/libsecurity-go/storage" 30 | ) 31 | 32 | const ( 33 | saltLen = 8 34 | 35 | rsaPrivateKeyFileName = "key.private" 36 | rsaPublicKeyFileName = "key.pub" 37 | ) 38 | 39 | var () 40 | 41 | func init() { 42 | } 43 | 44 | func usage() { 45 | _, file := filepath.Split(os.Args[0]) 46 | fmt.Fprintf(os.Stderr, "usage: %v.go\n", file) 47 | flag.PrintDefaults() 48 | os.Exit(2) 49 | } 50 | 51 | // Generate a new secure storage minimal file that includes the root user with 52 | // basic Account Management: the root user privilege and password 53 | func createBasicFile(stFilePath string, name string, pass string, key []byte) { 54 | saltStr, _ := salt.GetRandomSalt(saltLen) 55 | _, err := salt.GenerateSaltedPassword([]byte(pass), password.MinPasswordLength, password.MaxPasswordLength, saltStr, -1) 56 | if err != nil { 57 | log.Fatalf("Error: can't generate salted password for '%v' user, error: %v", name, err) 58 | } 59 | ul := en.New() 60 | ul.AddUser(name) 61 | amUser, _ := am.NewUserAm(am.SuperUserPermission, []byte(pass), saltStr, true) 62 | ul.AddPropertyToEntity(name, defs.AmPropertyName, amUser) 63 | ul.StoreInfo(stFilePath, key, false) 64 | } 65 | 66 | // Generate RSA public and private keys to the given file name 67 | func generateRSAKeys(rsaPrivateKeyFileName string, rsaPublicKeyFileName string) { 68 | privateKey, err := rsa.GenerateKey(rand.Reader, 4096) 69 | if err != nil { 70 | log.Fatalf("Error: can't generate rsa key, error: %v", err) 71 | } 72 | privateASN1 := x509.MarshalPKCS1PrivateKey(privateKey) 73 | privateBytes := pem.EncodeToMemory(&pem.Block{ 74 | Type: "RSA PRIVATE KEY", 75 | Bytes: privateASN1, 76 | }) 77 | ioutil.WriteFile(rsaPrivateKeyFileName, privateBytes, 0644) 78 | 79 | pubASN1, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) 80 | if err != nil { 81 | log.Fatalf("Error: can't generate rsa public key from the private key, error: %v", err) 82 | } 83 | pubBytes := pem.EncodeToMemory(&pem.Block{ 84 | Type: "RSA PUBLIC KEY", 85 | Bytes: pubASN1, 86 | }) 87 | ioutil.WriteFile(rsaPublicKeyFileName, pubBytes, 0644) 88 | fmt.Println("Generate RSA files:", rsaPrivateKeyFileName, "And", rsaPublicKeyFileName) 89 | } 90 | 91 | func main() { 92 | defaultRootPassword := defs.RootUserName 93 | 94 | secureKeyFileNamePath := flag.String("secure-key", "./secureKey", "secure key file path") 95 | loginFilePath := flag.String("storage-file", "./data.txt", "First storage file that includes the root user") 96 | rootPassword := flag.String("password", defaultRootPassword, "Root password") 97 | str := fmt.Sprintf("Generate RSA private/public files ('%s', '%s')", rsaPrivateKeyFileName, rsaPublicKeyFileName) 98 | generateRSA := flag.Bool("generate-rsa", false, str) 99 | flag.Parse() 100 | if flag.NArg() > 0 { 101 | usage() 102 | } 103 | 104 | if *rootPassword == defaultRootPassword { 105 | fmt.Printf("Error: The root password must be set (and not to '%v')\n", defaultRootPassword) 106 | usage() 107 | } 108 | err := password.CheckPasswordStrength(*rootPassword) 109 | if err != nil { 110 | log.Fatalf("Error: The root password must be more complex: %v", err) 111 | } 112 | 113 | key := ss.GetSecureKey(*secureKeyFileNamePath) 114 | createBasicFile(*loginFilePath, defs.RootUserName, *rootPassword, key) 115 | fmt.Println("The generated file name is:", *loginFilePath) 116 | if *generateRSA { 117 | generateRSAKeys(rsaPrivateKeyFileName, rsaPublicKeyFileName) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /otp/otpHotp_test.go: -------------------------------------------------------------------------------- 1 | package otp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func setStateHotp(ot *Hotp, state HotpRun) { 8 | ot.BaseOtp.Secret = state.hotp.BaseOtp.Secret 9 | ot.BaseOtp.digest = state.hotp.BaseOtp.digest 10 | ot.BaseOtp.Digits = state.hotp.BaseOtp.Digits 11 | ot.Count = state.hotp.Count 12 | } 13 | 14 | func testGetHotp(t *testing.T) *Hotp { 15 | hotp, err := NewHotp(BaseSecret, defaultStartCounter) 16 | if err != nil { 17 | t.Error("Test fail,", err) 18 | t.FailNow() 19 | } 20 | return hotp 21 | } 22 | 23 | // Verify that different counter values with the same counter seed as well as identical counter values with different counter seeds result with different OTPs and 24 | // identical counter values with identical counter seeds result with identical OTPs 25 | func Test_OtpTestHotpAtCount(t *testing.T) { 26 | hotp := testGetHotp(t) 27 | counterOffset := []int64{0, -1, 1, 12345} 28 | 29 | for _, offset := range counterOffset { 30 | for _, data := range referenceRunsHotp { 31 | setStateHotp(hotp, data) 32 | val, err := data.hotp.AtCount(data.counter + offset) 33 | if err != nil { 34 | t.Error("Test fail before running, illigal parameters:", err) 35 | continue 36 | } 37 | if offset == 0 { 38 | if val != data.result { 39 | t.Error("OTP did not change after incrementing the counter even though all other parameters, including the seed counter, are identical:", data, 40 | "original seed -", data.counter, ", curent seed -", data.counter+offset, 41 | "but the OTP -", data.result, "is different from the resultant OPT -", val) 42 | } else { 43 | t.Log("OTP calculated value for the same seed stays the same (as expected)") 44 | } 45 | 46 | } 47 | if offset != 0 { 48 | if val == data.result { 49 | t.Error("OTP value did not change when the seed counter changed (all other parameters remained identical) :", data, 50 | "original seed -", data.counter, ", curent seed -", data.counter+offset, 51 | "but the OTP calculated value was the same", val) 52 | } else { 53 | t.Log("OTP calculated value for different seed changed (as expected)") 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | // If all parameters are the same, the resultant OTP should be repetative 61 | // If the OTP request start counter was changed (either a different counter or the use use of .Next() or .New()) and 62 | // all other parameters are the same, the resultant OTP should be different 63 | func Test_OtpTestHotpNextNew(t *testing.T) { 64 | hotp := testGetHotp(t) 65 | funcCall := [](func() (string, error)){hotp.New, hotp.Next} 66 | 67 | for i, f := range funcCall { 68 | for _, data := range referenceRunsHotp { 69 | setStateHotp(hotp, data) 70 | val, err := f() 71 | if val == data.result { 72 | val, err = f() // give it another chance 73 | } 74 | if err != nil { 75 | t.Error("Test fail before running, illigal parameters:", err) 76 | continue 77 | } 78 | if val == data.result { 79 | t.Error("OTP value did not change when the seed counter changed", 80 | "(all other parameters remained identical). Using function:", i, "(0: .New(), 1: .Next())) must return different OTP values:", data, 81 | "but the OTP calculated value was the same", val) 82 | } else { 83 | // t.Log("OTP calculated value for different seed due to .next/.new call was changed (as expected)") 84 | } 85 | } 86 | } 87 | } 88 | 89 | // WIll check a loop of 11, from a referance counter of -5 to +5 90 | // The only time it should get the same results is when the counters are the same 91 | func Test_OtpTestHotpAccuracy(t *testing.T) { 92 | hotp := testGetHotp(t) 93 | shift := int64(5) 94 | 95 | for _, data := range referenceRunsHotp { 96 | data.hotp.Count = data.hotp.Count - shift - 1 97 | setStateHotp(hotp, data) 98 | for i := -shift; i < shift+1; i++ { 99 | val, err := data.hotp.Next() 100 | if err != nil { 101 | t.Error("Test fail before running, illigal parameters:", err) 102 | break 103 | } 104 | if i != 0 && val == data.result { 105 | t.Error("OTP value did not change when the seed counter changed", 106 | "(all other parameters remained identical) :", data, 107 | "but the OTP calculated value was the same:", val) 108 | } 109 | if i == 0 && val != data.result { 110 | t.Error("OTP value modified even though all parameters", 111 | "as well as the counter value and seed counter are identical: ", data, 112 | "OTP", val, "is different from the result OTP:", data.result) 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /restful/libsecurity-restful/libsecurity_test.go: -------------------------------------------------------------------------------- 1 | package libsecurityRestful 2 | 3 | import ( 4 | "testing" 5 | 6 | am "github.com/ibm-security-innovation/libsecurity-go/accounts" 7 | "github.com/ibm-security-innovation/libsecurity-go/acl" 8 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 9 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 10 | "github.com/ibm-security-innovation/libsecurity-go/ocra" 11 | "github.com/ibm-security-innovation/libsecurity-go/otp" 12 | "github.com/ibm-security-innovation/libsecurity-go/password" 13 | ) 14 | 15 | const () 16 | 17 | var ( 18 | St *LibsecurityRestful 19 | 20 | secret = []byte("ABCDEFGH12345678") 21 | salt = []byte("Salt") 22 | ) 23 | 24 | func init() { 25 | St = NewLibsecurityRestful() 26 | } 27 | 28 | // Verify that get property from undefined user returns an error 29 | // Verify that get property from user before setting the OTP property, returns an error 30 | // Verify that get property from user after setting the property returns the same property as was setted to the user 31 | // Verify that get property from user after removing the OTP property returns an error 32 | // Verify that get property from user after readding the OTP property returns OK 33 | // Verify that get property from user that was removed after OTP property was set, returns an error 34 | // Verify that Add a property to user, remove the user, generate a new user with the same name and try to get the property returns an error 35 | func testAddCheckRemoveUserProperty(t *testing.T, propertyName string, moduleData interface{}) { 36 | name := "name1" 37 | usersList := en.New() 38 | _, err := usersList.GetPropertyAttachedToEntity(name, propertyName) 39 | if err == nil { 40 | t.Errorf("Test fail, Recived module '%v' of undefined user '%v'", propertyName, name) 41 | } 42 | 43 | usersList.AddResource(name) 44 | _, err = usersList.GetPropertyAttachedToEntity(name, propertyName) 45 | if err == nil { 46 | t.Errorf("Test fail, Recived module '%v' of not registered yet module for user '%v'", propertyName, name) 47 | } 48 | 49 | usersList.AddPropertyToEntity(name, propertyName, moduleData) 50 | tmp, err := usersList.GetPropertyAttachedToEntity(name, propertyName) 51 | if err != nil { 52 | t.Errorf("Test fail, Error while feteching module '%v' from user '%v', error: %v", propertyName, name, err) 53 | } 54 | if moduleData != tmp { 55 | t.Errorf("Test fail, Added '%v' property '%v' is not equal to the fetched one '%v'", propertyName, moduleData, tmp) 56 | } 57 | 58 | usersList.RemovePropertyFromEntity(name, propertyName) 59 | _, err = usersList.GetPropertyAttachedToEntity(name, propertyName) 60 | if err == nil { 61 | t.Errorf("Test fail, Removed module '%v' from user '%v' was successfully fetched", propertyName, name) 62 | } 63 | 64 | usersList.AddPropertyToEntity(name, propertyName, moduleData) 65 | _, err = usersList.GetPropertyAttachedToEntity(name, propertyName) 66 | if err != nil { 67 | t.Errorf("Test fail, Error while feteching module '%v' from user '%v', error: %v", propertyName, name, err) 68 | } 69 | 70 | usersList.RemoveResource(name) 71 | _, err = usersList.GetPropertyAttachedToEntity(name, propertyName) 72 | if err == nil { 73 | t.Errorf("Test fail, Module '%v' of removed user '%v' was successfully fetched", propertyName, name) 74 | } 75 | err = usersList.AddPropertyToEntity(name, propertyName, moduleData) 76 | if err == nil { 77 | t.Errorf("Test fail, Atteched module '%v' to removed user '%v'", propertyName, name) 78 | } 79 | usersList.AddResource(name) 80 | _, err = usersList.GetPropertyAttachedToEntity(name, propertyName) 81 | if err == nil { 82 | t.Errorf("Test fail, Module '%v' was fetched before atttached to the user '%v'", propertyName, name) 83 | } 84 | } 85 | 86 | func Test_AddCheckRemoveOtpUserProperty(t *testing.T) { 87 | moduleData, _ := otp.NewSimpleOtpUser(secret, false) 88 | 89 | testAddCheckRemoveUserProperty(t, defs.OtpPropertyName, moduleData) 90 | } 91 | 92 | func Test_AddCheckRemovePwdUserProperty(t *testing.T) { 93 | moduleData, _ := password.NewUserPwd(secret, salt, false) 94 | 95 | testAddCheckRemoveUserProperty(t, defs.PwdPropertyName, moduleData) 96 | } 97 | 98 | func Test_AddCheckRemoveOcraUserProperty(t *testing.T) { 99 | moduleData, _ := ocra.NewOcraUser([]byte("12345678"), "OCRA-1:HOTP-SHA512-8:C-QH08-T1M-S064-PSHA256") 100 | 101 | testAddCheckRemoveUserProperty(t, defs.OcraPropertyName, moduleData) 102 | } 103 | 104 | func Test_AddCheckRemoveAMUserProperty(t *testing.T) { 105 | moduleData, _ := am.NewUserAm(am.SuperUserPermission, secret, salt, false) 106 | 107 | testAddCheckRemoveUserProperty(t, defs.AmPropertyName, moduleData) 108 | } 109 | 110 | func Test_AddCheckRemoveACLUserProperty(t *testing.T) { 111 | moduleData := acl.NewACL() 112 | 113 | testAddCheckRemoveUserProperty(t, defs.AclPropertyName, moduleData) 114 | } 115 | -------------------------------------------------------------------------------- /restful/accounts-restful/am_command.go: -------------------------------------------------------------------------------- 1 | package accountsRestful 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/emicklei/go-restful" 7 | am "github.com/ibm-security-innovation/libsecurity-go/accounts" 8 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 9 | ) 10 | 11 | const ( 12 | handleAuthenticateCommand = iota 13 | handleUserCommand 14 | handleUserPwdCommand 15 | ) 16 | 17 | var ( 18 | commandsToPath = []cr.ComamndsToPath{ 19 | {handleAuthenticateCommand, "%v"}, 20 | {handleUserCommand, "%v/{%v}"}, 21 | {handleUserPwdCommand, "%v/{%v}/%v"}, 22 | } 23 | 24 | urlCommands = make(cr.CommandToPath) 25 | ) 26 | 27 | func initCommandToPath() { 28 | for _, c := range commandsToPath { 29 | urlCommands[c.Command] = c.Path 30 | } 31 | } 32 | 33 | func (l AmRestful) setAmRoute(service *restful.WebService) { 34 | str := fmt.Sprintf(urlCommands[handleAuthenticateCommand], userPath) 35 | service.Route(service.PUT(str). 36 | To(l.restAm). 37 | Doc("Authenticate a user"). 38 | Operation("authenticate"). 39 | Reads(pUserData{})) 40 | 41 | str = fmt.Sprintf(urlCommands[handleAuthenticateCommand], logoutPath) 42 | service.Route(service.DELETE(str).To(l.restLogout). 43 | Doc("Logout the current user"). 44 | Operation("logout")) 45 | } 46 | 47 | func (l AmRestful) setFullRoute(service *restful.WebService) { 48 | str := fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 49 | service.Route(service.PUT(str). 50 | Filter(l.st.SuperUserFilter). 51 | To(l.restAddAm). 52 | Doc("Add Account Management"). 53 | Operation("AddNewAm"). 54 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 55 | Reads(privilegePwd{}). 56 | Writes(cr.URL{})) 57 | 58 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 59 | service.Route(service.GET(str). 60 | Filter(l.st.SameUserFilter). 61 | To(l.restGetAm). 62 | Doc("Get Account Management"). 63 | Operation("getAm"). 64 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 65 | Writes(am.AmUserInfo{})) 66 | 67 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 68 | service.Route(service.DELETE(str). 69 | Filter(l.st.SuperUserFilter). 70 | To(l.restDeleteAM). 71 | Doc("Remove Account Management"). 72 | Operation("deleteAm"). 73 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string"))) 74 | 75 | str = fmt.Sprintf(urlCommands[handleUserPwdCommand], usersPath, userIDParam, privilegePath) 76 | service.Route(service.PATCH(str). 77 | Filter(l.st.SuperUserFilter). 78 | To(l.restUpdatePrivilege). 79 | Doc("Update Account Management privilege"). 80 | Operation("updatePrivilege"). 81 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 82 | Reads(privilegeInfo{}). 83 | Writes(cr.URL{})) 84 | 85 | str = fmt.Sprintf(urlCommands[handleUserPwdCommand], usersPath, userIDParam, pwdPath) 86 | service.Route(service.PATCH(str). 87 | Filter(l.st.SameUserUpdatePasswordFilter). 88 | To(l.restUpdatePwd). 89 | Doc("Update Account Management password"). 90 | Operation("updatePwd"). 91 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 92 | Reads(cr.UpdateSecret{}). 93 | Writes(cr.URL{})) 94 | 95 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 96 | service.Route(service.PATCH(str). 97 | Filter(l.st.SuperUserFilter). 98 | To(l.restResetPwd). 99 | Doc("Reset user password"). 100 | Operation("resetPwd"). 101 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 102 | Writes(cr.Secret{})) 103 | 104 | str = fmt.Sprintf(urlCommands[handleAuthenticateCommand], verifyPath) 105 | service.Route(service.GET(str). 106 | Filter(l.st.VerifyToken). 107 | To(l.restVerifyToken). 108 | Doc("Verify token"). 109 | Operation("verifyToken")) 110 | } 111 | 112 | // RegisterFull : register the all the accounts interfaces to the RESTFul API container 113 | func (l AmRestful) RegisterFull(container *restful.Container) { 114 | servicePath = cr.ServicePathPrefix + cr.Version + amPrefix 115 | 116 | service := new(restful.WebService) 117 | service. 118 | Path(servicePath). 119 | Consumes(restful.MIME_JSON). 120 | Produces(restful.MIME_JSON) 121 | // .Doc("Account Management and Authentication") 122 | 123 | l.setAmRoute(service) 124 | l.setFullRoute(service) 125 | container.Add(service) 126 | } 127 | 128 | // RegisterBasic : register the accounts login/logout to the RESTFul API container 129 | func (l AmRestful) RegisterBasic(container *restful.Container) { 130 | servicePath = cr.ServicePathPrefix + cr.Version + amPrefix 131 | 132 | service := new(restful.WebService) 133 | service. 134 | Path(servicePath). 135 | Consumes(restful.MIME_JSON). 136 | Produces(restful.MIME_JSON) 137 | //.Doc("Account Management and Authentication") 138 | 139 | l.setAmRoute(service) 140 | container.Add(service) 141 | } 142 | -------------------------------------------------------------------------------- /restful/ocra-restful/ocra_command.go: -------------------------------------------------------------------------------- 1 | package ocraRestful 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/emicklei/go-restful" 7 | "github.com/ibm-security-innovation/libsecurity-go/ocra" 8 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 9 | ) 10 | 11 | const ( 12 | handleUserCommand = iota 13 | handleUserUpdateCommand 14 | verifyUserIdentityCommand 15 | ) 16 | 17 | var ( 18 | commandsToPath = []cr.ComamndsToPath{ 19 | {handleUserCommand, "%v/{%v}"}, 20 | {handleUserUpdateCommand, "%v/{%v}/%v"}, 21 | {verifyUserIdentityCommand, "%v/{%v}/%v"}, 22 | } 23 | 24 | urlCommands = make(cr.CommandToPath) 25 | ) 26 | 27 | func initCommandToPath() { 28 | for _, c := range commandsToPath { 29 | urlCommands[c.Command] = c.Path 30 | } 31 | } 32 | 33 | func (o OcraRestful) setRoute(service *restful.WebService) { 34 | str := fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 35 | service.Route(service.PUT(str). 36 | Filter(o.st.SuperUserFilter). 37 | To(o.restAddOcra). 38 | Doc("Add OCRA"). 39 | Operation("addOcra"). 40 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 41 | Reads(ocraUserData{}). 42 | Writes(cr.URL{})) 43 | 44 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 45 | service.Route(service.GET(str). 46 | Filter(o.st.SameUserFilter). 47 | To(o.restGetOcra). 48 | Doc("Get OCRA"). 49 | Operation("getOcra"). 50 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 51 | Writes(ocra.UserOcra{})) 52 | 53 | str = fmt.Sprintf(urlCommands[handleUserCommand], usersPath, userIDParam) 54 | service.Route(service.DELETE(str). 55 | Filter(o.st.SuperUserFilter). 56 | To(o.restDeleteOcra). 57 | Doc("Remove OCRA"). 58 | Operation("deleteOcra"). 59 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string"))) 60 | 61 | str = fmt.Sprintf(urlCommands[handleUserUpdateCommand], usersPath, userIDParam, keyToken) 62 | service.Route(service.PATCH(str). 63 | Filter(o.st.SuperUserFilter). 64 | To(o.restUpdateOcraKey). 65 | Doc("Update OCRA secret key"). 66 | Operation("updtaeKey"). 67 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 68 | Reads(cr.Secret{}). 69 | Writes(cr.URL{})) 70 | 71 | str = fmt.Sprintf(urlCommands[handleUserUpdateCommand], usersPath, userIDParam, ocraSuiteToken) 72 | service.Route(service.PATCH(str). 73 | Filter(o.st.SameUserFilter). 74 | To(o.restUpdateOcraSuite). 75 | Doc("Update OCRA suite"). 76 | Operation("updtaeOcraSuite"). 77 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 78 | Reads(cr.StringMessage{}). 79 | Writes(cr.URL{})) 80 | 81 | str = fmt.Sprintf(urlCommands[verifyUserIdentityCommand], usersPath, userIDParam, verifyUserIdentityChallengeToken) 82 | service.Route(service.GET(str). 83 | To(o.restVerifyOcraUserIdentityChallenge). 84 | Doc("Verify the user identity using OCRA, one way"). 85 | Operation("verifyUserIdentity"). 86 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 87 | Writes(ocraData{})) 88 | 89 | str = fmt.Sprintf(urlCommands[verifyUserIdentityCommand], usersPath, userIDParam, verifyUserIdentityOtpToken) 90 | service.Route(service.PUT(str). 91 | To(o.restVerifyOcraUserIdentityCheckOtp). 92 | Doc("Check the user OTP challenge response using OCRA"). 93 | Operation("verifyUserIdentityOtp"). 94 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 95 | Reads(ocraData{}). 96 | Writes(cr.Match{})) 97 | 98 | str = fmt.Sprintf(urlCommands[verifyUserIdentityCommand], usersPath, userIDParam, verifyUserIdentityMutualChallengeStep1Token) 99 | service.Route(service.PUT(str). 100 | To(o.restVerifyOcraUserIdentityMutualChallengeStep1). 101 | Doc("Verify the user identity using OCRA, mutual chalange, Step 1"). 102 | Operation("verifyUserIdentityMutualChallenge"). 103 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 104 | Reads(cr.StringMessage{}). 105 | Writes(ocraData{})) 106 | 107 | str = fmt.Sprintf(urlCommands[verifyUserIdentityCommand], usersPath, userIDParam, verifyUserIdentityMutualChallengeStep2Token) 108 | service.Route(service.PUT(str). 109 | To(o.restVerifyOcraUserIdentityMutualChallengeStep2). 110 | Doc("Verify the user identity using OCRA, mutual chalange, Step 2"). 111 | Operation("verifyUserIdentityMutualChallenge"). 112 | Param(service.PathParameter(userIDParam, userNameComment).DataType("string")). 113 | Reads(ocraData{}). 114 | Writes(cr.Match{})) 115 | } 116 | 117 | // RegisterBasic : register the OCRA to the RESTFul API container 118 | func (o OcraRestful) RegisterBasic(container *restful.Container) { 119 | servicePath = cr.ServicePathPrefix + cr.Version + ocraPrefix 120 | 121 | service := new(restful.WebService) 122 | service. 123 | Path(servicePath). 124 | Consumes(restful.MIME_JSON). 125 | Produces(restful.MIME_JSON) 126 | //.Doc("OATH Challenge-Response Algorithm") 127 | 128 | o.setRoute(service) 129 | container.Add(service) 130 | } 131 | -------------------------------------------------------------------------------- /restful/storage-restful/secureStorage_restful.go: -------------------------------------------------------------------------------- 1 | package storageRestful 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/emicklei/go-restful" 9 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 10 | "github.com/ibm-security-innovation/libsecurity-go/restful/libsecurity-restful" 11 | ss "github.com/ibm-security-innovation/libsecurity-go/storage" 12 | ) 13 | 14 | const ( 15 | sPrefix = "/securestorage" 16 | storagePath = "/storage" 17 | storageItemPath = "/item" 18 | 19 | secretIDParam = "secret" 20 | secretComment = "secret val" 21 | keyIDParam = "key-id" 22 | keyComment = "key val" 23 | ) 24 | 25 | var ( 26 | servicePath string // = cr.ServicePathPrefix + sPrefix 27 | 28 | checkSecretStrength = true // Allow only strength secrets 29 | ) 30 | 31 | type itemData struct { 32 | Key string 33 | Value string 34 | } 35 | 36 | type itemValue struct { 37 | Data string 38 | } 39 | 40 | // SRestful : Secure Storage restful structure 41 | type SRestful struct { 42 | st *libsecurityRestful.LibsecurityRestful 43 | } 44 | 45 | func init() { 46 | initCommandToPath() 47 | } 48 | 49 | // NewSsRestful : return a pointer to the secure storage Restful structure 50 | func NewSsRestful() *SRestful { 51 | return &SRestful{} 52 | } 53 | 54 | // SetData : initialize the Secure Storage Restful structure 55 | func (s *SRestful) SetData(stR *libsecurityRestful.LibsecurityRestful) { 56 | s.st = stR 57 | } 58 | 59 | func (s SRestful) getURLPath(request *restful.Request) cr.URL { 60 | return cr.URL{URL: fmt.Sprintf("%v", servicePath)} 61 | } 62 | 63 | func (s SRestful) setError(response *restful.Response, httpStatusCode int, err error) { 64 | data, _ := json.Marshal(cr.Error{Code: httpStatusCode, Message: fmt.Sprintf("%v", err)}) 65 | response.WriteErrorString(httpStatusCode, string(data)) 66 | } 67 | 68 | func (s SRestful) isSecureStorgaeValid(response *restful.Response) bool { 69 | if s.st.SecureStorage == nil { 70 | s.setError(response, http.StatusNotFound, fmt.Errorf("Error: Secure storage must be created first")) 71 | return false 72 | } 73 | return true 74 | } 75 | 76 | func (s SRestful) isSecretMatch(request *restful.Request, response *restful.Response) bool { 77 | if s.isSecureStorgaeValid(response) == false { 78 | return false 79 | } 80 | secret := request.HeaderParameter(secretIDParam) 81 | if s.st.SecureStorage.IsSecretMatch([]byte(secret)) == false { 82 | s.setError(response, http.StatusNotFound, fmt.Errorf("Error: The password that you entered does not match the secure storage password")) 83 | return false 84 | } 85 | return true 86 | } 87 | 88 | func (s *SRestful) restCreateSecureStorage(request *restful.Request, response *restful.Response) { 89 | secret := request.HeaderParameter(secretIDParam) 90 | data, err := ss.NewStorage([]byte(secret), checkSecretStrength) 91 | if err != nil { 92 | s.setError(response, http.StatusBadRequest, err) 93 | return 94 | } 95 | s.st.SecureStorage = data 96 | response.WriteHeaderAndEntity(http.StatusCreated, s.getURLPath(request)) 97 | } 98 | 99 | func (s SRestful) restDeleteSecureStorage(request *restful.Request, response *restful.Response) { 100 | if s.isSecureStorgaeValid(response) == false { 101 | return 102 | } 103 | if s.isSecretMatch(request, response) == false { 104 | return 105 | } 106 | s.st.SecureStorage = nil 107 | response.WriteHeader(http.StatusNoContent) 108 | } 109 | 110 | func (s SRestful) restGetSecureStorage(request *restful.Request, response *restful.Response) { 111 | if s.isSecureStorgaeValid(response) == false { 112 | return 113 | } 114 | if s.isSecretMatch(request, response) == false { 115 | return 116 | } 117 | response.WriteHeaderAndEntity(http.StatusOK, s.st.SecureStorage.GetDecryptStorageData()) 118 | } 119 | 120 | func (s SRestful) restAddItemToSecureStorage(request *restful.Request, response *restful.Response) { 121 | if s.isSecureStorgaeValid(response) == false { 122 | return 123 | } 124 | if s.isSecretMatch(request, response) == false { 125 | return 126 | } 127 | var item itemData 128 | err := request.ReadEntity(&item) 129 | if err != nil { 130 | s.setError(response, http.StatusBadRequest, err) 131 | return 132 | } 133 | 134 | err = s.st.SecureStorage.AddItem(item.Key, item.Value) 135 | if err != nil { 136 | s.setError(response, http.StatusInternalServerError, err) 137 | return 138 | } 139 | response.WriteHeaderAndEntity(http.StatusCreated, s.getURLPath(request)) 140 | } 141 | 142 | func (s SRestful) restGetItemFromSecureStorage(request *restful.Request, response *restful.Response) { 143 | if s.isSecureStorgaeValid(response) == false { 144 | return 145 | } 146 | if s.isSecretMatch(request, response) == false { 147 | return 148 | } 149 | key := request.HeaderParameter(keyIDParam) 150 | 151 | val, err := s.st.SecureStorage.GetItem(key) 152 | if err != nil { 153 | s.setError(response, http.StatusNotFound, err) 154 | return 155 | } 156 | response.WriteHeaderAndEntity(http.StatusOK, itemValue{val}) 157 | } 158 | 159 | func (s SRestful) restDeleteItemFromSecureStorage(request *restful.Request, response *restful.Response) { 160 | if s.isSecureStorgaeValid(response) == false { 161 | return 162 | } 163 | if s.isSecretMatch(request, response) == false { 164 | return 165 | } 166 | key := request.HeaderParameter(keyIDParam) 167 | err := s.st.SecureStorage.RemoveItem(key) 168 | if err != nil { 169 | s.setError(response, http.StatusNotFound, err) 170 | return 171 | } 172 | response.WriteHeader(http.StatusNoContent) 173 | } 174 | -------------------------------------------------------------------------------- /swagger-dist/forewind/app/v1/securestorage: -------------------------------------------------------------------------------- 1 | { 2 | "swaggerVersion": "1.2", 3 | "apiVersion": "", 4 | "basePath": "/", 5 | "resourcePath": "/forewind/app/v1/securestorage", 6 | "apis": [ 7 | { 8 | "path": "/forewind/app/v1/securestorage/storage", 9 | "description": "Secure Storage", 10 | "operations": [ 11 | { 12 | "type": "common_restful.Url", 13 | "method": "PUT", 14 | "summary": "Create a new secure storage", 15 | "nickname": "CreateSecureStorage", 16 | "parameters": [ 17 | { 18 | "type": "string", 19 | "paramType": "header", 20 | "name": "secret", 21 | "description": "secret val", 22 | "required": false, 23 | "allowMultiple": false 24 | } 25 | ], 26 | "produces": [ 27 | "application/json" 28 | ], 29 | "consumes": [ 30 | "application/json" 31 | ] 32 | }, 33 | { 34 | "type": "void", 35 | "method": "DELETE", 36 | "summary": "Remove the current secure storage", 37 | "nickname": "RemoveSecureStorage", 38 | "parameters": [ 39 | { 40 | "type": "string", 41 | "paramType": "header", 42 | "name": "secret", 43 | "description": "secret val", 44 | "required": false, 45 | "allowMultiple": false 46 | } 47 | ], 48 | "produces": [ 49 | "application/json" 50 | ], 51 | "consumes": [ 52 | "application/json" 53 | ] 54 | }, 55 | { 56 | "type": "void", 57 | "method": "GET", 58 | "summary": "Get a secure storage", 59 | "nickname": "getSecureStorage", 60 | "parameters": [ 61 | { 62 | "type": "string", 63 | "paramType": "header", 64 | "name": "secret", 65 | "description": "secret val", 66 | "required": false, 67 | "allowMultiple": false 68 | } 69 | ], 70 | "produces": [ 71 | "application/json" 72 | ], 73 | "consumes": [ 74 | "application/json" 75 | ] 76 | } 77 | ] 78 | }, 79 | { 80 | "path": "/forewind/app/v1/securestorage/item", 81 | "description": "Secure Storage", 82 | "operations": [ 83 | { 84 | "type": "common_restful.Url", 85 | "method": "PATCH", 86 | "summary": "Add a new item to the secure storage", 87 | "nickname": "addANewItemToTheSecureStorage", 88 | "parameters": [ 89 | { 90 | "type": "string", 91 | "paramType": "header", 92 | "name": "secret", 93 | "description": "secret val", 94 | "required": false, 95 | "allowMultiple": false 96 | }, 97 | { 98 | "type": "storage_restful.itemData", 99 | "paramType": "body", 100 | "name": "body", 101 | "description": "", 102 | "required": true, 103 | "allowMultiple": false 104 | } 105 | ], 106 | "produces": [ 107 | "application/json" 108 | ], 109 | "consumes": [ 110 | "application/json" 111 | ] 112 | }, 113 | { 114 | "type": "storage_restful.itemValue", 115 | "method": "GET", 116 | "summary": "Get an item from the secure storage", 117 | "nickname": "getAnItemFromTheSecureStorage", 118 | "parameters": [ 119 | { 120 | "type": "string", 121 | "paramType": "header", 122 | "name": "secret", 123 | "description": "secret val", 124 | "required": false, 125 | "allowMultiple": false 126 | }, 127 | { 128 | "type": "string", 129 | "paramType": "header", 130 | "name": "key-id", 131 | "description": "key val", 132 | "required": false, 133 | "allowMultiple": false 134 | } 135 | ], 136 | "produces": [ 137 | "application/json" 138 | ], 139 | "consumes": [ 140 | "application/json" 141 | ] 142 | }, 143 | { 144 | "type": "void", 145 | "method": "DELETE", 146 | "summary": "Delete item from a secure storage", 147 | "nickname": "deleteItemFromSecureStorage", 148 | "parameters": [ 149 | { 150 | "type": "string", 151 | "paramType": "header", 152 | "name": "secret", 153 | "description": "secret val", 154 | "required": false, 155 | "allowMultiple": false 156 | }, 157 | { 158 | "type": "string", 159 | "paramType": "header", 160 | "name": "key-id", 161 | "description": "key val", 162 | "required": false, 163 | "allowMultiple": false 164 | } 165 | ], 166 | "produces": [ 167 | "application/json" 168 | ], 169 | "consumes": [ 170 | "application/json" 171 | ] 172 | } 173 | ] 174 | } 175 | ], 176 | "models": { 177 | "common_restful.Url": { 178 | "id": "common_restful.Url", 179 | "required": [ 180 | "Url" 181 | ], 182 | "properties": { 183 | "Url": { 184 | "type": "string" 185 | } 186 | } 187 | }, 188 | "storage_restful.itemData": { 189 | "id": "storage_restful.itemData", 190 | "required": [ 191 | "Key", 192 | "Value" 193 | ], 194 | "properties": { 195 | "Key": { 196 | "type": "string" 197 | }, 198 | "Value": { 199 | "type": "string" 200 | } 201 | } 202 | }, 203 | "storage_restful.itemValue": { 204 | "id": "storage_restful.itemValue", 205 | "required": [ 206 | "Data" 207 | ], 208 | "properties": { 209 | "Data": { 210 | "type": "string" 211 | } 212 | } 213 | } 214 | } 215 | } -------------------------------------------------------------------------------- /restful/otp-restful/otp_restful.go: -------------------------------------------------------------------------------- 1 | package otpRestful 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/emicklei/go-restful" 9 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 10 | "github.com/ibm-security-innovation/libsecurity-go/otp" 11 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 12 | "github.com/ibm-security-innovation/libsecurity-go/restful/libsecurity-restful" 13 | ) 14 | 15 | const ( 16 | otpPrefix = "/otp" 17 | usersPath = "/users" 18 | 19 | userIDParam = "user-name" 20 | userNameComment = "user name" 21 | blockedStateToken = "blocked-state" 22 | blockedStateParam = "blocked-state" 23 | verifyHotpTypeParam = "verify-hotp" 24 | verifyTotpTypeParam = "verify-totp" 25 | 26 | originToken = "Origin" 27 | 28 | trueStr = "true" 29 | falseStr = "false" 30 | 31 | blockedStr = "blocked" 32 | notblockedStr = "not blocked" 33 | ) 34 | 35 | var ( 36 | servicePath string // = cr.ServicePathPrefix + otpPrefix 37 | checkSecretStrength = true // Allow only strength passwords 38 | ) 39 | 40 | // OtpRestful : OtpRestful structure 41 | type OtpRestful struct { 42 | st *libsecurityRestful.LibsecurityRestful 43 | } 44 | 45 | type userState struct { 46 | Blocked bool 47 | } 48 | 49 | func init() { 50 | initCommandToPath() 51 | } 52 | 53 | // NewOtpRestful : return a pointer to the OtpRestful structure 54 | func NewOtpRestful() *OtpRestful { 55 | return &OtpRestful{} 56 | } 57 | 58 | // SetData : initialize the OtpRestful structure 59 | func (u *OtpRestful) SetData(stR *libsecurityRestful.LibsecurityRestful) { 60 | u.st = stR 61 | } 62 | 63 | func (u OtpRestful) getURLPath(request *restful.Request, name string) cr.URL { 64 | // return cr.URL{URL: fmt.Sprintf("%v%v/%v", request.Request.Header.Get(originToken), servicePath, name)} 65 | return cr.URL{URL: fmt.Sprintf("%v/%v", servicePath, name)} 66 | } 67 | 68 | func (u OtpRestful) setError(response *restful.Response, httpStatusCode int, err error) { 69 | data, _ := json.Marshal(cr.Error{Code: httpStatusCode, Message: fmt.Sprintf("%v", err)}) 70 | response.WriteErrorString(httpStatusCode, string(data)) 71 | } 72 | 73 | func (u OtpRestful) getOtp(request *restful.Request, response *restful.Response) *otp.UserInfoOtp { 74 | userName := request.PathParameter(userIDParam) 75 | data, err := cr.GetPropertyData(userName, defs.OtpPropertyName, u.st.UsersList) 76 | if err != nil { 77 | u.setError(response, http.StatusNotFound, err) 78 | return nil 79 | } 80 | return data.(*otp.UserInfoOtp) 81 | } 82 | 83 | func (u OtpRestful) restAddOtp(request *restful.Request, response *restful.Response) { 84 | var secret cr.Secret 85 | name := request.PathParameter(userIDParam) 86 | err := request.ReadEntity(&secret) 87 | if err != nil { 88 | u.setError(response, http.StatusBadRequest, err) 89 | return 90 | } 91 | data, err := otp.NewSimpleOtpUser([]byte(secret.Secret), checkSecretStrength) 92 | if err != nil { 93 | u.setError(response, http.StatusBadRequest, err) 94 | return 95 | } 96 | err = u.st.UsersList.AddPropertyToEntity(name, defs.OtpPropertyName, data) 97 | if err != nil { 98 | u.setError(response, http.StatusNotFound, err) 99 | return 100 | } 101 | response.WriteHeaderAndEntity(http.StatusCreated, u.getURLPath(request, name)) 102 | } 103 | 104 | func (u OtpRestful) restGetOtp(request *restful.Request, response *restful.Response) { 105 | data := u.getOtp(request, response) 106 | if data == nil { 107 | return 108 | } 109 | response.WriteHeaderAndEntity(http.StatusOK, data) 110 | } 111 | 112 | func (u OtpRestful) restDeleteOtp(request *restful.Request, response *restful.Response) { 113 | name := request.PathParameter(userIDParam) 114 | err := u.st.UsersList.RemovePropertyFromEntity(name, defs.OtpPropertyName) 115 | if err != nil { 116 | u.setError(response, http.StatusNotFound, err) 117 | } else { 118 | response.WriteHeader(http.StatusNoContent) 119 | } 120 | } 121 | 122 | func (u OtpRestful) restIsOtpBlocked(request *restful.Request, response *restful.Response) { 123 | var state userState 124 | 125 | data := u.getOtp(request, response) 126 | if data == nil { 127 | return 128 | } 129 | ok, err := data.IsOtpUserBlocked() 130 | state.Blocked = ok 131 | if err != nil { 132 | u.setError(response, http.StatusNotFound, err) 133 | return 134 | } 135 | response.WriteHeaderAndEntity(http.StatusOK, state) 136 | } 137 | 138 | func (u OtpRestful) restSetOtpBlockedState(request *restful.Request, response *restful.Response) { 139 | var blockedState userState 140 | 141 | name := request.PathParameter(userIDParam) 142 | err := request.ReadEntity(&blockedState) 143 | if err != nil { 144 | u.setError(response, http.StatusBadRequest, err) 145 | return 146 | } 147 | data := u.getOtp(request, response) 148 | if data == nil { 149 | return 150 | } 151 | err = data.SetOtpUserBlockedState(blockedState.Blocked) 152 | if err != nil { 153 | u.setError(response, http.StatusNotFound, err) 154 | return 155 | } 156 | response.WriteHeaderAndEntity(http.StatusOK, u.getURLPath(request, name)) 157 | } 158 | 159 | func (u OtpRestful) restVerifyOtpHotpUserCode(request *restful.Request, response *restful.Response) { 160 | u.verifyUserOtp(request, response, otp.HotpType) 161 | } 162 | 163 | func (u OtpRestful) restVerifyOtpTotpUserCode(request *restful.Request, response *restful.Response) { 164 | u.verifyUserOtp(request, response, otp.TotpType) 165 | } 166 | 167 | func (u OtpRestful) verifyUserOtp(request *restful.Request, response *restful.Response, otpType otp.TypeOfOtp) { 168 | var secret cr.Secret 169 | 170 | err := request.ReadEntity(&secret) 171 | if err != nil { 172 | u.setError(response, http.StatusBadRequest, err) 173 | return 174 | } 175 | data := u.getOtp(request, response) 176 | if data == nil { 177 | return 178 | } 179 | ok, err := data.VerifyOtpUserCode(secret.Secret, otpType) 180 | res := cr.Match{Match: ok, Message: cr.NoMessageStr} 181 | if ok == false && err != nil { 182 | res.Message = fmt.Sprintf("%v", err) 183 | } 184 | response.WriteHeaderAndEntity(http.StatusOK, res) 185 | } 186 | -------------------------------------------------------------------------------- /salt/salt.go: -------------------------------------------------------------------------------- 1 | // Package salt : The salt package provides salting services for anyone who uses passwords 2 | package salt 3 | 4 | import ( 5 | "bytes" 6 | "crypto/rand" 7 | "crypto/sha1" 8 | "fmt" 9 | "hash" 10 | "io" 11 | 12 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 13 | ) 14 | 15 | const ( 16 | defaultOutputLen = 128 17 | defaultSaltLen = 8 18 | defaultNumOfItterations = 1 19 | 20 | minOutputLen = 6 21 | minSaltLen = 0 22 | maxSaltLen = 128 23 | minNumOfItterations = 1 24 | ) 25 | 26 | var defaultHashFunc = sha1.New 27 | 28 | // Salt : structure that holds all the parameters relevant to handle salting of password 29 | type Salt struct { 30 | Secret []byte 31 | Salt []byte 32 | OutputLen int // Number of digits in the code. Default is 6 33 | Iterations int // Number of iterations to run the hash function, Default is 64 34 | Digest func() hash.Hash // Digest type, Default is sha1 35 | } 36 | 37 | func (s Salt) String() string { 38 | ret := fmt.Sprintf("Salt info: secret: %v, salt: %v, iterations: %v, output len: %v, digest: %v", 39 | string(s.Secret), string(s.Salt), s.Iterations, s.OutputLen, s.Digest) 40 | return ret 41 | } 42 | 43 | func isOutputLenValid(val int) error { 44 | if val < minOutputLen { 45 | return fmt.Errorf("Salt struct is not valid, The output length %v is less than the minimum %v", val, minOutputLen) 46 | } 47 | return nil 48 | } 49 | 50 | func isDigestValid(digest func() hash.Hash) error { 51 | if digest == nil { 52 | return fmt.Errorf("Salt struct is not valid, It must have a hash function, but the current hash is nil") 53 | } 54 | return nil 55 | } 56 | 57 | func isSecretValid(secret []byte, minSecretLen int, maxSecretLen int) error { 58 | if len(secret) < minSecretLen || len(secret) > maxSecretLen { 59 | return fmt.Errorf("Secret string has illegal length %v, The length must be between %v and %v", len(secret), minSecretLen, maxSecretLen) 60 | } 61 | return nil 62 | } 63 | 64 | func isSaltValid(salt []byte) error { 65 | if len(salt) < minSaltLen || len(salt) > maxSaltLen { 66 | return fmt.Errorf("Salt string has illegal length %v, The length must be between %v and %v", len(salt), minSaltLen, maxSaltLen) 67 | } 68 | return nil 69 | } 70 | 71 | func isNumOfIterationsValid(val int) error { 72 | if val < minNumOfItterations { 73 | return fmt.Errorf("Salt struct is not valid, The number of iterations %v is less than the minimum %v", val, minNumOfItterations) 74 | } 75 | return nil 76 | } 77 | 78 | func (s Salt) isValid(minSecretLen int, maxSecretLen int) error { 79 | err := isSecretValid(s.Secret, minSecretLen, maxSecretLen) 80 | if err != nil { 81 | return err 82 | } 83 | err = isSaltValid(s.Salt) 84 | if err != nil { 85 | return err 86 | } 87 | err = isOutputLenValid(s.OutputLen) 88 | if err != nil { 89 | return err 90 | } 91 | err = isNumOfIterationsValid(s.Iterations) 92 | if err != nil { 93 | return err 94 | } 95 | err = isDigestValid(s.Digest) 96 | if err != nil { 97 | return err 98 | } 99 | return nil 100 | } 101 | 102 | // NewSalt : The default Salt: use sha1, output length 16 bytes 103 | func NewSalt(secret []byte, minSecretLen int, maxSecretLen int, salt []byte) (*Salt, error) { 104 | err := isSecretValid(secret, minSecretLen, maxSecretLen) 105 | if err != nil { 106 | return nil, err 107 | } 108 | err = isSaltValid(salt) 109 | if err != nil { 110 | return nil, err 111 | } 112 | return &Salt{ 113 | secret, 114 | salt, 115 | defaultOutputLen, 116 | defaultNumOfItterations, 117 | defaultHashFunc, 118 | }, nil 119 | } 120 | 121 | // GenerateSaltedPassword : generate a salted password using the given password and salt information 122 | func GenerateSaltedPassword(pwd []byte, minSecretLen int, maxSecretLen int, saltData []byte, passwordLen int) ([]byte, error) { 123 | mySalt, err := NewSalt([]byte(pwd), minSecretLen, maxSecretLen, saltData) 124 | if err != nil { 125 | return nil, err 126 | } 127 | if passwordLen != -1 { 128 | mySalt.OutputLen = passwordLen 129 | } 130 | return mySalt.Generate(minSecretLen, maxSecretLen) 131 | } 132 | 133 | // GeneratePasswordWithRndSalt : Return a generated salted password and the used salt from a given password 134 | func GeneratePasswordWithRndSalt(pass string, minSecretLen int, maxSecretLen int) ([]byte, []byte, error) { 135 | salt, err := GetRandomSalt(defaultSaltLen) 136 | if err != nil { 137 | return nil, nil, err 138 | } 139 | s, err := NewSalt([]byte(pass), minSecretLen, maxSecretLen, salt) 140 | if err != nil { 141 | return nil, nil, err 142 | } 143 | saltedPass, err := s.Generate(minSecretLen, maxSecretLen) 144 | return saltedPass, salt, err 145 | } 146 | 147 | // Generate : Return the encrypted data for a given salt and secret 148 | // The way to add salt is: secret + salt 149 | //TODO: output len from right or from left 150 | func (s Salt) Generate(minSecretLen int, maxSecretLen int) ([]byte, error) { 151 | err := s.isValid(minSecretLen, maxSecretLen) 152 | if err != nil { 153 | return []byte(""), err 154 | } 155 | h := s.Digest() 156 | 157 | data := s.Secret 158 | for i := 0; i < s.Iterations; i++ { 159 | data = append(data, s.Salt...) 160 | h.Write(data) 161 | data = h.Sum(nil) 162 | } 163 | logger.Trace.Println("data:", data) 164 | len := len(data) 165 | if len > s.OutputLen { 166 | len = s.OutputLen 167 | } 168 | ret := data[0:len] 169 | return ret, nil 170 | } 171 | 172 | // Match : compare 2 given salt information 173 | func (s Salt) Match(ref []byte, minSecretLen int, maxSecretLen int) (bool, error) { 174 | res, _ := s.Generate(minSecretLen, maxSecretLen) 175 | ok := bytes.Equal(res, ref) 176 | return ok, nil 177 | } 178 | 179 | // GetRandomSalt : generate a random salt with the given length 180 | func GetRandomSalt(size int) ([]byte, error) { 181 | if size < 0 { 182 | return nil, fmt.Errorf("Size %v not valid, Size must be larger than 1", size) 183 | } 184 | buf := make([]byte, size) 185 | _, err := io.ReadFull(rand.Reader, buf) 186 | if err != nil { 187 | panic(fmt.Errorf("Random read failed: %v", err)) 188 | } 189 | return buf, nil 190 | } 191 | -------------------------------------------------------------------------------- /storage/secureStorage_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "reflect" 10 | "testing" 11 | 12 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 13 | ) 14 | 15 | const ( 16 | baseSecret = "AaBb@1234567890123456" 17 | baseSecret1 = "AaBc;1234567890111111" 18 | ) 19 | 20 | var ( 21 | RandomStr string 22 | ) 23 | 24 | func init() { 25 | val := make([]byte, 20) 26 | io.ReadFull(rand.Reader, val) 27 | RandomStr = string(bytes.Replace(val, []byte{0}, []byte{'a'}, -1)) 28 | } 29 | 30 | // Verify that adding the same item key with different values reflects it the results 31 | // Verify that getting a removed item will return an error 32 | func Test_checkAddRemoveItemToSecureStorage(t *testing.T) { 33 | keys := []string{"k1", "k2 is the best key in here", "k1", RandomStr} 34 | values := []string{"v1", "v2 is the answer to the question of k2", "v3", RandomStr} 35 | secret := []byte(baseSecret) 36 | 37 | s, _ := NewStorage(secret, true) 38 | for i, key := range keys { 39 | s.AddItem(key, values[i]) 40 | val, err := s.GetItem(key) 41 | if err != nil { 42 | t.Errorf("Test fail: Valid key: '%v' (idx %v) was not presented, error: %v", key, i, err) 43 | } else if val != values[i] { 44 | t.Errorf("Test fail: The recieved value: '%v' was not as expected '%v'", []byte(val), []byte(values[i])) 45 | } 46 | } 47 | for i, key := range keys { 48 | val, err := s.GetItem(key) 49 | if err != nil { 50 | t.Errorf("Test fail: Valid key: '%v' (idx %v) was not presented, error: %v", key, i, err) 51 | } else if val != values[i] && i != 0 { 52 | t.Errorf("Test fail: The recieved value: '%v' was not as expected '%v'", val, values[i]) 53 | } else if val == values[i] && i == 0 { 54 | t.Errorf("Test fail: The recieved value: '%v' for key '%v' was changed", val, key) 55 | } 56 | } 57 | s1 := s.GetDecryptStorageData() 58 | for key, val := range s1.Data { 59 | sVal, err := s.GetItem(key) 60 | if err != nil { 61 | t.Errorf("Test fail: decrypted key '%v' was not found in the storage, error: %v", key, err) 62 | } 63 | if val != sVal { 64 | t.Errorf("Test fail: key '%v' decrepted val '%v' is not equal to the one in the storage %v\n", key, val, sVal) 65 | t.Errorf("Storage %v", s.GetDecryptStorageData()) 66 | t.Errorf("Decrypted storage %v", s1) 67 | } 68 | } 69 | for _, key := range keys { 70 | s.RemoveItem(key) 71 | _, err := s.GetItem(key) 72 | if err == nil { 73 | t.Errorf("Test fail: Removed key: '%v' return a value", key) 74 | } 75 | } 76 | } 77 | 78 | // Verify that secure storage saved to file is equal to the one loaded from that file 79 | // Verify that wrong secret return an error when reading a secure storage 80 | // Verify that wrong signature return an error when reading a secure storage 81 | func Test_checkStoreLoadSecureStorageFile(t *testing.T) { 82 | keys := []string{"k1", "k2", "k3"} 83 | values := []string{"v1", "v2", "v3"} 84 | secret := []byte(baseSecret) 85 | fileName := "./tmp.txt" 86 | secret1 := []byte(baseSecret1) 87 | 88 | s, _ := NewStorage(secret, true) 89 | for i, key := range keys { 90 | s.AddItem(key, values[i]) 91 | } 92 | s.StoreInfo(fileName) 93 | defer os.Remove(fileName) 94 | s1, err := LoadInfo(fileName, secret) 95 | if err != nil { 96 | t.Errorf("Test fail: Read secure storage from file fail, error: %v", err) 97 | t.FailNow() 98 | } 99 | if reflect.DeepEqual(s.Data, s1.Data) == false { 100 | s1.secret = secret // to allow decryption 101 | t.Errorf("Test fail: The original secure storage: '%v' is not equal after store and load it: '%v'", 102 | s.GetDecryptStorageData(), s1.GetDecryptStorageData()) 103 | } 104 | 105 | _, err = LoadInfo(fileName, secret1) 106 | if err == nil { 107 | t.Errorf("Test fail: Successfully read secure storage from file while using wrong secret") 108 | } 109 | 110 | // currpt the file data 111 | data, _ := ioutil.ReadFile(fileName) 112 | data[40] = 'x' 113 | data[41] = 'x' 114 | ioutil.WriteFile(fileName, data, FilePermissions) 115 | 116 | _, err = LoadInfo(fileName, secret) 117 | if err == nil { 118 | t.Errorf("Test fail: Successfully read secure storage from file while the file was currpted") 119 | } 120 | } 121 | 122 | // Verify that when the version of the saved file is not equal to the loaded one, an error is generated 123 | func Test_checkVersionInSecureStorageFile(t *testing.T) { 124 | keys := []string{"k1", "k2", "k3"} 125 | values := []string{"v1", "v2", "v3"} 126 | secret := []byte(baseSecret) 127 | fileName := "./tmp.txt" 128 | 129 | s, _ := NewStorage(secret, true) 130 | s.Version = "new 1" 131 | for i, key := range keys { 132 | s.AddItem(key, values[i]) 133 | } 134 | s.StoreInfo(fileName) 135 | defer os.Remove(fileName) 136 | _, err := LoadInfo(fileName, secret) 137 | if err == nil { 138 | t.Errorf("Test fail: The saved and loaded versions must not be equal") 139 | t.FailNow() 140 | } 141 | } 142 | 143 | func Test_corners(t *testing.T) { 144 | logger.Init(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard) 145 | s, _ := NewStorage([]byte(baseSecret), true) 146 | s.AddItem("key", "value") 147 | logger.Trace.Println("The storage is", s.GetDecryptStorageData()) 148 | if s.IsSecretMatch([]byte("a1234")) == true { 149 | t.Errorf("Test fail: wrong secret match to the storage secret") 150 | } 151 | fileName := "tmp.txt" 152 | defer os.Remove(fileName) 153 | fileName1 := "tmp1.txt" 154 | defer os.Remove(fileName1) 155 | ioutil.WriteFile(fileName, []byte("12345678111111111111111111111111111111111111111111111"), os.ModePerm) 156 | ioutil.WriteFile(fileName1, []byte("12345678111111111111111111111111111111111111111111111 "), os.ModePerm) 157 | v1 := GetSecureKey(fileName) 158 | v2 := GetSecureKey(fileName) 159 | v3 := GetSecureKey(fileName1) 160 | if string(v1) != string(v2) { 161 | t.Errorf("Test fail: the same GetSecureKey return 2 different results") 162 | } 163 | if string(v1) == string(v3) { 164 | t.Errorf("Test fail: different inputs to GetSecureKey return the same results") 165 | } 166 | _, err := NewStorage([]byte("1234"), true) 167 | if err == nil { 168 | t.Errorf("Test fail: simple secret was accepted") 169 | } 170 | } -------------------------------------------------------------------------------- /restful/password-restful/pwd_restful.go: -------------------------------------------------------------------------------- 1 | package passwordRestful 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/emicklei/go-restful" 9 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 10 | "github.com/ibm-security-innovation/libsecurity-go/password" 11 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 12 | "github.com/ibm-security-innovation/libsecurity-go/restful/libsecurity-restful" 13 | "github.com/ibm-security-innovation/libsecurity-go/salt" 14 | ) 15 | 16 | const ( 17 | pwdPrefix = "/password" 18 | usersPath = "/users" 19 | userIDParam = "user-name" 20 | userNameComment = "user name" 21 | resetUserPwdPath = "reset" 22 | ) 23 | 24 | var ( 25 | servicePath string // = cr.ServicePathPrefix + pwdPrefix 26 | 27 | saltLen = 10 28 | 29 | checkPasswordStrength = true // Allow only strength passwords 30 | ) 31 | 32 | // PwdRestful : Pwd restful structure 33 | type PwdRestful struct { 34 | st *libsecurityRestful.LibsecurityRestful 35 | saltStr []byte 36 | } 37 | 38 | type secretData struct { 39 | Password string 40 | } 41 | 42 | type userState struct { 43 | Blocked bool 44 | } 45 | 46 | func init() { 47 | initCommandToPath() 48 | } 49 | 50 | // NewPwdRestful : return a pointer to the PwdRestful structure 51 | func NewPwdRestful() *PwdRestful { 52 | saltStr, _ := salt.GetRandomSalt(saltLen) 53 | return &PwdRestful{nil, saltStr} 54 | } 55 | 56 | // SetData : initialize the PwdRestful structure 57 | func (p *PwdRestful) SetData(stR *libsecurityRestful.LibsecurityRestful) { 58 | p.st = stR 59 | } 60 | 61 | func (p PwdRestful) getURLPath(request *restful.Request, name string) cr.URL { 62 | return cr.URL{URL: fmt.Sprintf("%v/%v", servicePath, name)} 63 | } 64 | 65 | func (p PwdRestful) setError(response *restful.Response, httpStatusCode int, err error) { 66 | data, _ := json.Marshal(cr.Error{Code: httpStatusCode, Message: fmt.Sprintf("%v", err)}) 67 | response.WriteErrorString(httpStatusCode, string(data)) 68 | } 69 | 70 | func (p PwdRestful) getPwdData(request *restful.Request, response *restful.Response) *password.UserPwd { 71 | userName := request.PathParameter(userIDParam) 72 | data, err := cr.GetPropertyData(userName, defs.PwdPropertyName, p.st.UsersList) 73 | if err != nil { 74 | p.setError(response, http.StatusNotFound, err) 75 | return nil 76 | } 77 | return data.(*password.UserPwd) 78 | } 79 | 80 | func (p PwdRestful) restAddPwd(request *restful.Request, response *restful.Response) { 81 | var secret secretData 82 | name := request.PathParameter(userIDParam) 83 | 84 | err := request.ReadEntity(&secret) 85 | if err != nil { 86 | p.setError(response, http.StatusBadRequest, err) 87 | return 88 | } 89 | data, err := password.NewUserPwd([]byte(secret.Password), p.saltStr, checkPasswordStrength) 90 | if err != nil { 91 | p.setError(response, http.StatusBadRequest, err) 92 | return 93 | } 94 | err = p.st.UsersList.AddPropertyToEntity(name, defs.PwdPropertyName, data) 95 | if err != nil { 96 | p.setError(response, http.StatusNotFound, err) 97 | return 98 | } 99 | response.WriteHeaderAndEntity(http.StatusCreated, p.getURLPath(request, name)) 100 | } 101 | 102 | func (p PwdRestful) restGetPwd(request *restful.Request, response *restful.Response) { 103 | data := p.getPwdData(request, response) 104 | if data == nil { 105 | return 106 | } 107 | response.WriteHeaderAndEntity(http.StatusOK, data) 108 | } 109 | 110 | func (p PwdRestful) restDeletePwd(request *restful.Request, response *restful.Response) { 111 | name := request.PathParameter(userIDParam) 112 | data := p.getPwdData(request, response) 113 | if data == nil { 114 | return 115 | } 116 | err := p.st.UsersList.RemovePropertyFromEntity(name, defs.PwdPropertyName) 117 | if err != nil { 118 | p.setError(response, http.StatusBadRequest, err) 119 | } else { 120 | response.WriteHeader(http.StatusNoContent) 121 | } 122 | } 123 | 124 | func (p PwdRestful) restUpdatePassword(request *restful.Request, response *restful.Response) { 125 | var secrets cr.UpdateSecret 126 | name := request.PathParameter(userIDParam) 127 | err := request.ReadEntity(&secrets) 128 | if err != nil { 129 | p.setError(response, http.StatusBadRequest, err) 130 | return 131 | } 132 | data := p.getPwdData(request, response) 133 | if data == nil { 134 | return 135 | } 136 | tPwd, _ := salt.GenerateSaltedPassword([]byte(secrets.OldPassword), password.MinPasswordLength, password.MaxPasswordLength, p.saltStr, -1) 137 | pass := password.GetHashedPwd(tPwd) 138 | if err != nil { 139 | p.setError(response, http.StatusBadRequest, err) 140 | return 141 | } 142 | _, err = data.UpdatePassword(pass, []byte(secrets.NewPassword), checkPasswordStrength) 143 | if err != nil { 144 | p.setError(response, http.StatusBadRequest, err) 145 | return 146 | } 147 | response.WriteHeaderAndEntity(http.StatusCreated, p.getURLPath(request, name)) 148 | } 149 | 150 | func (p PwdRestful) restResetPassword(request *restful.Request, response *restful.Response) { 151 | data := p.getPwdData(request, response) 152 | if data == nil { 153 | return 154 | } 155 | newPwd, err := data.ResetPassword() 156 | if err != nil { 157 | p.setError(response, http.StatusBadRequest, err) 158 | return 159 | } 160 | response.WriteHeaderAndEntity(http.StatusCreated, secretData{string(newPwd)}) 161 | } 162 | 163 | func (p PwdRestful) restVerifyPassword(request *restful.Request, response *restful.Response) { 164 | var secret secretData 165 | err := request.ReadEntity(&secret) 166 | tPwd, _ := salt.GenerateSaltedPassword([]byte(secret.Password), password.MinPasswordLength, password.MaxPasswordLength, p.saltStr, -1) 167 | pass := password.GetHashedPwd(tPwd) 168 | if err != nil { 169 | p.setError(response, http.StatusBadRequest, err) 170 | return 171 | } 172 | data := p.getPwdData(request, response) 173 | if data == nil { 174 | return 175 | } 176 | err = data.IsPasswordMatch(pass) 177 | ok := true 178 | if err != nil { 179 | ok = false 180 | } 181 | res := cr.Match{Match: ok, Message: cr.NoMessageStr} 182 | if ok == false && err != nil { 183 | res.Message = fmt.Sprintf("%v", err) 184 | } 185 | response.WriteHeaderAndEntity(http.StatusOK, res) 186 | } 187 | -------------------------------------------------------------------------------- /restful/acl-restful/acl_command.go: -------------------------------------------------------------------------------- 1 | package aclRestful 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/emicklei/go-restful" 7 | "github.com/ibm-security-innovation/libsecurity-go/acl" 8 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 9 | ) 10 | 11 | const ( 12 | handleAclCommand = iota 13 | handlePermissionCommand 14 | getAllPermissionCommand 15 | getAllPermissionsOfEntityCommand 16 | ) 17 | 18 | var ( 19 | commandsToPath = []cr.ComamndsToPath{ 20 | {handleAclCommand, "%v/{%v}"}, 21 | {handlePermissionCommand, "%v/{%v}/%v/{%v}/%v/{%v}"}, 22 | {getAllPermissionCommand, "%v/%v/{%v}"}, 23 | {getAllPermissionsOfEntityCommand, "%v/{%v}/%v/{%v}"}, 24 | } 25 | urlCommands = make(cr.CommandToPath) 26 | ) 27 | 28 | func initCommandToPath() { 29 | for _, c := range commandsToPath { 30 | urlCommands[c.Command] = c.Path 31 | } 32 | } 33 | 34 | func (a AclRestful) setRoute(service *restful.WebService) { 35 | str := fmt.Sprintf(urlCommands[handleAclCommand], resourceToken, resourceNameParam) 36 | service.Route(service.PUT(str). 37 | Filter(a.st.SuperUserFilter). 38 | To(a.restAddAclToResource). 39 | Doc("Add ACL to resource"). 40 | Operation("addAclToResource"). 41 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string")). 42 | Reads(acl.Acl{}). 43 | Writes(cr.URL{})) 44 | 45 | str = fmt.Sprintf(urlCommands[handleAclCommand], resourceToken, resourceNameParam) 46 | service.Route(service.GET(str). 47 | Filter(a.st.SameUserFilter). 48 | To(a.restGetAclOfResource). 49 | Doc("Get ACL attached to resource"). 50 | Operation("getAcl"). 51 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string")). 52 | Writes(acl.Acl{})) 53 | 54 | str = fmt.Sprintf(urlCommands[handleAclCommand], resourceToken, resourceNameParam) 55 | service.Route(service.DELETE(str). 56 | Filter(a.st.SuperUserFilter). 57 | To(a.restDeleteAclFromResource). 58 | Doc("Remove ACL from resource"). 59 | Operation("deleteAcl"). 60 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string"))) 61 | } 62 | 63 | func (a AclRestful) setUsersRoute(service *restful.WebService) { 64 | str := fmt.Sprintf(urlCommands[handlePermissionCommand], entityToken, entityNameParam, resourceToken, resourceNameParam, permissionsToken, permissionParam) 65 | service.Route(service.PUT(str). 66 | Filter(a.st.SuperUserFilter). 67 | To(a.restSetPermission). 68 | Doc("Grant the premission to the given entity for a given resource"). 69 | Operation("setPermission"). 70 | Param(service.PathParameter(entityNameParam, entityComment).DataType("string")). 71 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string")). 72 | Param(service.PathParameter(permissionParam, permissionComment).DataType("string")). 73 | Writes(cr.URL{})) 74 | 75 | str = fmt.Sprintf(urlCommands[handlePermissionCommand], entityToken, entityNameParam, resourceToken, resourceNameParam, permissionsToken, permissionParam) 76 | service.Route(service.GET(str). 77 | Filter(a.st.SameUserFilter). 78 | To(a.restCheckPermission). 79 | Doc("Check if the entity has the given permission to the resource"). 80 | Operation("checkEntityPermissionToResource"). 81 | Param(service.PathParameter(entityNameParam, entityComment).DataType("string")). 82 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string")). 83 | Param(service.PathParameter(permissionParam, permissionComment).DataType("string")). 84 | Writes(cr.Match{})) 85 | 86 | str = fmt.Sprintf(urlCommands[handlePermissionCommand], entityToken, entityNameParam, resourceToken, resourceNameParam, permissionsToken, permissionParam) 87 | service.Route(service.DELETE(str). 88 | Filter(a.st.SuperUserFilter). 89 | To(a.restDeletePermission). 90 | Doc("Revoke the permission of the given entity for the given resource"). 91 | Operation("deleteEntityPermissionFromAResource"). 92 | Param(service.PathParameter(entityNameParam, entityComment).DataType("string")). 93 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string")). 94 | Param(service.PathParameter(permissionParam, permissionComment).DataType("string"))) 95 | 96 | str = fmt.Sprintf(urlCommands[getAllPermissionCommand], permissionsToken, resourceToken, resourceNameParam) 97 | service.Route(service.GET(str). 98 | Filter(a.st.SameUserFilter). 99 | To(a.restGetAllPermissions). 100 | Doc("Get all the permissions of the given resource"). 101 | Operation("getUserGroupPermissions"). 102 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string"))) 103 | 104 | str = fmt.Sprintf(urlCommands[getAllPermissionsOfEntityCommand], entityToken, entityNameParam, resourceToken, resourceNameParam) 105 | service.Route(service.GET(str). 106 | Filter(a.st.SameUserFilter). 107 | To(a.restGetAllPermissionsOfEntity). 108 | Doc("Get all the permissions of the entity"). 109 | Operation("getAllEntityPermission"). 110 | Param(service.PathParameter(entityNameParam, entityComment).DataType("string")). 111 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string"))) 112 | 113 | str = fmt.Sprintf(urlCommands[getAllPermissionsOfEntityCommand], resourceToken, resourceNameParam, permissionsToken, permissionParam) 114 | service.Route(service.GET(str). 115 | Filter(a.st.SuperUserFilter). 116 | To(a.restGetWhoUsesAResourcePermission). 117 | Doc("Get all the entities that have the permission to the resource"). 118 | Operation("getAllEntitiesOfPermission"). 119 | Param(service.PathParameter(resourceNameParam, resourceComment).DataType("string")). 120 | Param(service.PathParameter(permissionParam, permissionComment).DataType("string"))) 121 | } 122 | 123 | // RegisterBasic : register the ACL to the RESTFul API container 124 | func (a AclRestful) RegisterBasic(container *restful.Container) { 125 | servicePath = cr.ServicePathPrefix + cr.Version + aclPrefix 126 | 127 | service := new(restful.WebService) 128 | service. 129 | Path(servicePath). 130 | Consumes(restful.MIME_JSON). 131 | Produces(restful.MIME_JSON) 132 | // .Doc("Access Control List") 133 | a.setRoute(service) 134 | a.setUsersRoute(service) 135 | container.Add(service) 136 | } 137 | -------------------------------------------------------------------------------- /salt/salt_test.go: -------------------------------------------------------------------------------- 1 | package salt 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "crypto/sha256" 7 | "encoding/hex" 8 | "fmt" 9 | "io/ioutil" 10 | "hash" 11 | "testing" 12 | 13 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 14 | ) 15 | 16 | // TODO not tested: static external tests for iterations other than 1 (I couldn't find an online site) 17 | 18 | const ( 19 | testMinPwdLen = 4 20 | testMaxPwdLen = 255 21 | ) 22 | 23 | type SaltRun struct { 24 | salt *Salt 25 | result []byte 26 | } 27 | 28 | func (s SaltRun) String() string { 29 | return fmt.Sprintf("%v, result: %v", s.salt, s.result) 30 | } 31 | 32 | var BaseSecret = []byte("ABCD") 33 | var BaseSalt = []byte("A1B2") 34 | 35 | var referenceRunsSalt []SaltRun 36 | 37 | func init() { 38 | referenceRunsSalt = initRefSalt() 39 | } 40 | 41 | func initRefSalt() []SaltRun { 42 | var refRunsSalt []SaltRun 43 | secrets := []string{"", "ABCD", "hello"} 44 | salts := []string{"", "A1B2", "a12b34"} 45 | hashes := []func() hash.Hash{sha1.New, sha256.New, md5.New, nil} 46 | maxOutputLength := minOutputLen + 2 47 | 48 | for _, sc := range secrets { 49 | for _, sl := range salts { 50 | for _, d := range hashes { 51 | for ol := 0; ol < maxOutputLength; ol++ { 52 | for i := minNumOfItterations - 1; i < minNumOfItterations*100; i += 10 { 53 | salt, err := NewSalt([]byte(sc), testMinPwdLen, testMaxPwdLen, []byte(sl)) 54 | serr := isSecretValid([]byte(sc), testMinPwdLen, testMaxPwdLen) 55 | slerr := isSaltValid([]byte(sl)) 56 | if err == nil && (serr != nil || slerr != nil) { 57 | fmt.Println("Error while initializing") 58 | panic(fmt.Errorf("Initialize failed: initialize was done successfully but the input is invalid: %v", salt)) 59 | } 60 | if err != nil && serr == nil && slerr == nil { 61 | fmt.Println("Error while initializing") 62 | panic(fmt.Errorf("Initialize failed: input is valid %v, but error found: %v", salt, err)) 63 | } 64 | if err == nil { 65 | salt.OutputLen = ol 66 | salt.Digest = d 67 | salt.Iterations = i 68 | res, err := salt.Generate(testMinPwdLen, testMaxPwdLen) 69 | derr := isDigestValid(d) 70 | olerr := isOutputLenValid(ol) 71 | ierr := isNumOfIterationsValid(i) 72 | if err != nil && derr == nil && olerr == nil && ierr == nil { 73 | fmt.Println("Error while initializing") 74 | panic(fmt.Errorf("Initialize failed: Try to generate salted code for valid input %v, but error found: %v", salt, err)) 75 | } else if err == nil && (derr != nil || olerr != nil || ierr != nil) { 76 | fmt.Println("Error while initializing") 77 | panic(fmt.Errorf("Initialize failed salted code was generated successfully for invalid input: %v\n", salt)) // " secret (%v), salt(%v), digest (%v), output len (%v)\n", sc, sl, d, ol)) 78 | } else if err == nil { 79 | refRunsSalt = append(refRunsSalt, SaltRun{salt, res}) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | return refRunsSalt 88 | } 89 | 90 | // Verify that Different parameters (secret keys, salt, output len, input, digests) result with different generated password 91 | func Test_SaltParamesChanged(t *testing.T) { 92 | res := make(map[string]SaltRun) 93 | for _, data := range referenceRunsSalt { 94 | _, exists := res[string(data.result)] 95 | if exists { 96 | t.Error("Runs with different salt parameters return the same password:\nFirst run:", res[string(data.result)], "\nSecond run:", data) 97 | } else { 98 | res[string(data.result)] = data 99 | } 100 | } 101 | } 102 | 103 | // Verify that the same parameters (secret keys, salt, output len, input, digests) result with the same generated password 104 | func Test_SaltRepetitation(t *testing.T) { 105 | for _, data := range referenceRunsSalt { 106 | ok, _ := data.salt.Match(data.result, testMinPwdLen, testMaxPwdLen) 107 | if !ok { 108 | t.Error("Runs with the same salt parameters:", data, "return a different password") 109 | } 110 | } 111 | } 112 | 113 | type testHash struct { 114 | Digest func() hash.Hash 115 | Iteration int 116 | Password string 117 | } 118 | 119 | // Results are from http://www.lorem-ipsum.co.uk/hasher.php 120 | func Test_StaticCalculationSalting(t *testing.T) { 121 | var testsForABCDA1B2 = []testHash{{sha1.New, 1, "f877eed103f74c751952861e0630e643c4ec1eaa"}, 122 | {md5.New, 1, "dcfdf079664f00c2ad0d1e348b070bd5"}, 123 | {sha256.New, 1, "a193d7d1ba2253b712d13a0dd27bd7dfddcf04a6c8d904ae7e0e9ba2ced0f8fb"}} 124 | 125 | for i, test := range testsForABCDA1B2 { 126 | salt, err := NewSalt([]byte("ABCD"), 4, 128, []byte("A1B2")) 127 | if err != nil { 128 | t.Error("Test fail: Can't initialize Salt, error:", err) 129 | t.FailNow() 130 | } 131 | salt.Iterations = test.Iteration 132 | salt.Digest = test.Digest 133 | ref, err := hex.DecodeString(test.Password) 134 | if err != nil { 135 | t.Error("Test fail, can't convert", test.Password, "to bytes") 136 | } 137 | ok, err := salt.Match(ref, testMinPwdLen, testMaxPwdLen) 138 | if err != nil { 139 | t.Error("Test fail, error:", err) 140 | } else if !ok { 141 | t.Error("Test", i, "Fail: Expected external password", ref, 142 | "did not matched calculated password") 143 | } 144 | } 145 | } 146 | 147 | func Test_RandomSalt(t *testing.T) { 148 | for i := -10; i < 1000; i += 10 { 149 | salt, err := GetRandomSalt(i) 150 | if err == nil && i < 0 { 151 | t.Error("Test fail: get random salt:", salt, "for size of:", i, ", but it is illegal") 152 | } else if err != nil && i >= 0 { 153 | t.Error("Test fail: Generating of random salt for size", i, " fail, error:", err) 154 | } 155 | } 156 | } 157 | 158 | 159 | // Test corners: String, logger etc 160 | func Test_corners(t *testing.T) { 161 | pwd := []byte("ABCD12"); 162 | ilegalPwd := "AB" 163 | salt := []byte("salt123") 164 | minSecret := 4 165 | maxSecret := 10 166 | 167 | logger.Init(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard) 168 | salt1, _ := NewSalt(pwd, minSecret, maxSecret, salt) 169 | logger.Trace.Println("The salt info is", salt1) 170 | _, err := GenerateSaltedPassword(pwd, minSecret, maxSecret, salt, len(pwd)) 171 | if err != nil { 172 | t.Error("Test fail: Can't generate salted password, error:", err) 173 | } 174 | _, _, err = GeneratePasswordWithRndSalt(ilegalPwd, minSecret, maxSecret) 175 | if err == nil { 176 | t.Error("Test fail: Successfully generated password with random salt for ilegal password", ilegalPwd) 177 | } 178 | _, _, err = GeneratePasswordWithRndSalt(string(pwd), minSecret, maxSecret) 179 | if err != nil { 180 | t.Error("Test fail: Can't generate password with random salt, error:", err) 181 | } 182 | } -------------------------------------------------------------------------------- /swagger-dist/forewind/app/v1/password: -------------------------------------------------------------------------------- 1 | { 2 | "swaggerVersion": "1.2", 3 | "apiVersion": "", 4 | "basePath": "/", 5 | "resourcePath": "/forewind/app/v1/password", 6 | "apis": [ 7 | { 8 | "path": "/forewind/app/v1/password/users/{user-name}", 9 | "description": "", 10 | "operations": [ 11 | { 12 | "type": "common_restful.Url", 13 | "method": "PUT", 14 | "summary": "Add Password", 15 | "nickname": "AddPwd", 16 | "parameters": [ 17 | { 18 | "type": "string", 19 | "paramType": "path", 20 | "name": "user-name", 21 | "description": "user name", 22 | "required": true, 23 | "allowMultiple": false 24 | }, 25 | { 26 | "type": "password_restful.secretData", 27 | "paramType": "body", 28 | "name": "body", 29 | "description": "", 30 | "required": true, 31 | "allowMultiple": false 32 | } 33 | ], 34 | "produces": [ 35 | "application/json" 36 | ], 37 | "consumes": [ 38 | "application/json" 39 | ] 40 | }, 41 | { 42 | "type": "password.UserPwd", 43 | "method": "GET", 44 | "summary": "Get Password", 45 | "nickname": "getPwd", 46 | "parameters": [ 47 | { 48 | "type": "string", 49 | "paramType": "path", 50 | "name": "user-name", 51 | "description": "user name", 52 | "required": true, 53 | "allowMultiple": false 54 | } 55 | ], 56 | "produces": [ 57 | "application/json" 58 | ], 59 | "consumes": [ 60 | "application/json" 61 | ] 62 | }, 63 | { 64 | "type": "void", 65 | "method": "DELETE", 66 | "summary": "Remove Password", 67 | "nickname": "deletePwd", 68 | "parameters": [ 69 | { 70 | "type": "string", 71 | "paramType": "path", 72 | "name": "user-name", 73 | "description": "user name", 74 | "required": true, 75 | "allowMultiple": false 76 | } 77 | ], 78 | "produces": [ 79 | "application/json" 80 | ], 81 | "consumes": [ 82 | "application/json" 83 | ] 84 | }, 85 | { 86 | "type": "common_restful.Url", 87 | "method": "PATCH", 88 | "summary": "Update Password", 89 | "nickname": "updatePassword", 90 | "parameters": [ 91 | { 92 | "type": "string", 93 | "paramType": "path", 94 | "name": "user-name", 95 | "description": "user name", 96 | "required": true, 97 | "allowMultiple": false 98 | }, 99 | { 100 | "type": "common_restful.UpdateSecret", 101 | "paramType": "body", 102 | "name": "body", 103 | "description": "", 104 | "required": true, 105 | "allowMultiple": false 106 | } 107 | ], 108 | "produces": [ 109 | "application/json" 110 | ], 111 | "consumes": [ 112 | "application/json" 113 | ] 114 | }, 115 | { 116 | "type": "common_restful.Match", 117 | "method": "POST", 118 | "summary": "Verify that a given password is as expected", 119 | "nickname": "verifyPassword", 120 | "parameters": [ 121 | { 122 | "type": "string", 123 | "paramType": "path", 124 | "name": "user-name", 125 | "description": "user name", 126 | "required": true, 127 | "allowMultiple": false 128 | }, 129 | { 130 | "type": "password_restful.secretData", 131 | "paramType": "body", 132 | "name": "body", 133 | "description": "", 134 | "required": true, 135 | "allowMultiple": false 136 | } 137 | ], 138 | "produces": [ 139 | "application/json" 140 | ], 141 | "consumes": [ 142 | "application/json" 143 | ] 144 | } 145 | ] 146 | }, 147 | { 148 | "path": "/forewind/app/v1/password/users/{user-name}/reset", 149 | "description": "", 150 | "operations": [ 151 | { 152 | "type": "password_restful.secretData", 153 | "method": "GET", 154 | "summary": "Reset password", 155 | "nickname": "resetPassword", 156 | "parameters": [ 157 | { 158 | "type": "string", 159 | "paramType": "path", 160 | "name": "user-name", 161 | "description": "user name", 162 | "required": true, 163 | "allowMultiple": false 164 | } 165 | ], 166 | "produces": [ 167 | "application/json" 168 | ], 169 | "consumes": [ 170 | "application/json" 171 | ] 172 | } 173 | ] 174 | } 175 | ], 176 | "models": { 177 | "password_restful.secretData": { 178 | "id": "password_restful.secretData", 179 | "required": [ 180 | "Password" 181 | ], 182 | "properties": { 183 | "Password": { 184 | "type": "string" 185 | } 186 | } 187 | }, 188 | "common_restful.Url": { 189 | "id": "common_restful.Url", 190 | "required": [ 191 | "Url" 192 | ], 193 | "properties": { 194 | "Url": { 195 | "type": "string" 196 | } 197 | } 198 | }, 199 | "password.UserPwd": { 200 | "id": "password.UserPwd", 201 | "required": [ 202 | "Password", 203 | "Salt", 204 | "Expiration", 205 | "ErrorsCounter", 206 | "OneTimePwd", 207 | "OldPasswords" 208 | ], 209 | "properties": { 210 | "Password": { 211 | "type": "array", 212 | "items": { 213 | "$ref": "integer" 214 | } 215 | }, 216 | "Salt": { 217 | "type": "array", 218 | "items": { 219 | "$ref": "integer" 220 | } 221 | }, 222 | "Expiration": { 223 | "type": "string", 224 | "format": "date-time" 225 | }, 226 | "ErrorsCounter": { 227 | "type": "integer", 228 | "format": "int32" 229 | }, 230 | "OneTimePwd": { 231 | "type": "boolean" 232 | }, 233 | "OldPasswords": { 234 | "type": "array", 235 | "items": { 236 | "$ref": "password.UserPwd.OldPasswords" 237 | } 238 | } 239 | } 240 | }, 241 | "integer": { 242 | "id": "integer", 243 | "properties": {} 244 | }, 245 | "password.UserPwd.OldPasswords": { 246 | "id": "password.UserPwd.OldPasswords", 247 | "properties": {} 248 | }, 249 | "common_restful.UpdateSecret": { 250 | "id": "common_restful.UpdateSecret", 251 | "required": [ 252 | "OldPassword", 253 | "NewPassword" 254 | ], 255 | "properties": { 256 | "OldPassword": { 257 | "type": "string" 258 | }, 259 | "NewPassword": { 260 | "type": "string" 261 | } 262 | } 263 | }, 264 | "common_restful.Match": { 265 | "id": "common_restful.Match", 266 | "required": [ 267 | "Match", 268 | "Message" 269 | ], 270 | "properties": { 271 | "Match": { 272 | "type": "boolean" 273 | }, 274 | "Message": { 275 | "type": "string" 276 | } 277 | } 278 | } 279 | } 280 | } -------------------------------------------------------------------------------- /swagger-dist/lib/highlight.7.3.pack.js: -------------------------------------------------------------------------------- 1 | var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(//gm,">")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("")}while(o!=u.node);r.splice(q,1);while(q'+L[0]+""}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return''+r.value+""}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+=""}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"
")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.xml=function(a){var c="[A-Za-z0-9\\._:-]+";var b={eW:true,c:[{cN:"attribute",b:c,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},b]}]}}(hljs);hljs.LANGUAGES.json=function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}}(hljs); -------------------------------------------------------------------------------- /restful/common-restful/common_restful.go: -------------------------------------------------------------------------------- 1 | package commonRestful 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "sort" 9 | "strings" 10 | 11 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 12 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 13 | ) 14 | 15 | const ( 16 | // HTTPGetStr : HTTP GET token 17 | HTTPGetStr = "GET" 18 | // HTTPPostStr : HTTP POST token 19 | HTTPPostStr = "POST" 20 | // HTTPPutStr : HTTP PUT token 21 | HTTPPutStr = "PUT" 22 | // HTTPDeleteStr : HTTP DELETE token 23 | HTTPDeleteStr = "DELETE" 24 | // HTTPPatchStr : HTTP PATCH token 25 | HTTPPatchStr = "PATCH" 26 | 27 | // GetMessageStr : string to get parameters from the HTP request 28 | GetMessageStr = "get-data" 29 | 30 | // SetCookieStr : token use to set the cookie 31 | SetCookieStr = "Set-Cookie" 32 | // AccessToken : token use to set the AccessToken 33 | AccessToken = "AccessToken" 34 | // NoMessageStr : define the empty message 35 | NoMessageStr = "" 36 | 37 | // VersionPath : define the version path 38 | VersionPath = "/version" 39 | ) 40 | 41 | var ( 42 | // ServicePathPrefix : prefix to be use by the RESTFul API 43 | ServicePathPrefix = "/forewind/app" 44 | // Version : the current RESTFul version 45 | Version = "/v1" 46 | 47 | // testCookieStr : for testing purposes 48 | testCookieStr string // = "" 49 | 50 | // EmptyStr : the defined EmptyStr 51 | EmptyStr = StringMessage{""} 52 | ) 53 | 54 | // CommandToPath : hash map to convert between command and the relevant path 55 | type CommandToPath map[int]string 56 | 57 | // ComamndsToPath : Convert between command index and the relevant path 58 | type ComamndsToPath struct { 59 | Command int 60 | Path string 61 | } 62 | 63 | // Secret : secret struct definition 64 | type Secret struct { 65 | Secret string 66 | } 67 | 68 | // UpdateSecret : UpdateSecret struct definition 69 | type UpdateSecret struct { 70 | OldPassword string 71 | NewPassword string 72 | } 73 | 74 | // URL : Uel struct definition 75 | type URL struct { 76 | URL string 77 | } 78 | 79 | // Match : Match struct definition for OK and when fail, include the message to pass 80 | type Match struct { 81 | Match bool 82 | Message string // in case of error 83 | } 84 | 85 | // Error : Error struct definition: the code and the relevant message 86 | type Error struct { 87 | Code int 88 | Message string 89 | } 90 | 91 | // StringMessage : StringMessage struct definition 92 | type StringMessage struct { 93 | Str string 94 | } 95 | 96 | // FileData : the file path 97 | type FileData struct { 98 | FilePath string 99 | } 100 | 101 | // SecureFile : SecureFile struct definition: file path and the associated secret 102 | type SecureFile struct { 103 | FilePath string 104 | Secret string 105 | } 106 | 107 | // ConvertCommandToRequest : Remove all the {} from the command string so it could be used for request 108 | func ConvertCommandToRequest(cmd string) string { 109 | d := strings.Replace(cmd, "{", "", -1) 110 | return strings.Replace(d, "}", "", -1) 111 | } 112 | 113 | // GetResponse : parse the http response 114 | func GetResponse(response *http.Response, err error) (int, string, error) { 115 | if err != nil { 116 | return -1, "", err 117 | } 118 | defer response.Body.Close() 119 | contents, err := ioutil.ReadAll(response.Body) 120 | if err != nil { 121 | return -1, "", err 122 | } 123 | return response.StatusCode, string(contents), nil 124 | } 125 | 126 | // HTTPDataMethod : extract the http data method and atach the cookie 127 | func HTTPDataMethod(method string, url string, data string) (int, string, error) { 128 | client := &http.Client{} 129 | request, err := http.NewRequest(method, url, strings.NewReader(data)) 130 | if err != nil { 131 | fmt.Println("Error in: HTTPDataMethod, error:", err) 132 | } 133 | request.Header.Set("Content-Type", "application/json") 134 | request.AddCookie(&http.Cookie{Name: AccessToken, Value: testCookieStr, Path: "/"}) 135 | response, err := client.Do(request) 136 | return GetResponse(response, err) 137 | } 138 | 139 | // RemoveSpaces : clean the given string from all white spaces, \r and \n 140 | func RemoveSpaces(inStr string) string { 141 | d := strings.Replace(inStr, " ", "", -1) 142 | d = strings.Replace(d, "\n", "", -1) 143 | return strings.Replace(d, "\r", "", -1) 144 | } 145 | 146 | // GetExpectedData : check if the response data is as expected 147 | func GetExpectedData(sData string, okJ interface{}) (bool, string, string, Error, error) { 148 | var e Error 149 | var err error 150 | errFound := false 151 | var res string 152 | var exp string 153 | 154 | switch okJ.(type) { 155 | case URL: 156 | var resURL URL 157 | json.Unmarshal([]byte(sData), &resURL) 158 | if len(resURL.URL) == 0 { 159 | errFound = true 160 | } 161 | res = resURL.URL 162 | exp = okJ.(URL).URL 163 | case []string: 164 | var per []string 165 | json.Unmarshal([]byte(sData), &per) 166 | sort.Strings(per) 167 | data := okJ.([]string) 168 | sort.Strings(data) 169 | for _, p := range data { 170 | exp += " " + p 171 | } 172 | for _, p := range per { 173 | res += " " + p 174 | } 175 | case Match: 176 | var matchOk Match 177 | json.Unmarshal([]byte(sData), &matchOk) 178 | if matchOk.Match == okJ.(Match).Match { // if it matched, ignore the message (error) in the comparison 179 | res = fmt.Sprintf("%v", matchOk.Match) 180 | exp = fmt.Sprintf("%v", okJ.(Match).Match) 181 | } else { 182 | res = fmt.Sprintf("%v", matchOk) 183 | exp = fmt.Sprintf("%v", okJ.(Match)) 184 | } 185 | case StringMessage: 186 | res = sData 187 | exp = okJ.(StringMessage).Str 188 | if exp == GetMessageStr { // get data to be used later 189 | exp = res 190 | } 191 | case Error: 192 | var errStr Error 193 | json.Unmarshal([]byte(sData), &errStr) 194 | if errStr.Code == 0 { 195 | errFound = true 196 | } 197 | res = fmt.Sprintf("%v", errStr.Code) 198 | exp = fmt.Sprintf("%v", okJ.(Error).Code) 199 | case FileData: 200 | var fileData FileData 201 | json.Unmarshal([]byte(sData), &fileData) 202 | if len(fileData.FilePath) == 0 { 203 | errFound = true 204 | } 205 | res = fmt.Sprintf("%v", fileData.FilePath) 206 | exp = fmt.Sprintf("%v", okJ.(FileData).FilePath) 207 | case SecureFile: 208 | var fileData SecureFile 209 | json.Unmarshal([]byte(sData), &fileData) 210 | if len(fileData.FilePath) == 0 { 211 | errFound = true 212 | } 213 | res = fmt.Sprintf("%v-%v", fileData.FilePath, fileData.Secret) 214 | exp = fmt.Sprintf("%v-%v", okJ.(SecureFile).FilePath, okJ.(SecureFile).Secret) 215 | default: 216 | return false, exp, res, e, err 217 | } 218 | 219 | if errFound == true { 220 | err = json.Unmarshal([]byte(sData), &e) 221 | } 222 | return true, exp, res, e, err 223 | } 224 | 225 | // TestSetCookie : set the cookie string to the given parameter 226 | func TestSetCookie(cookieStr string) { 227 | testCookieStr = cookieStr 228 | logger.Trace.Println("Set cookie to:", testCookieStr) 229 | } 230 | 231 | // GetPropertyData : extract the property data from the relevant module 232 | func GetPropertyData(userName string, propertyName string, usersList *en.EntityManager) (interface{}, error) { 233 | data, err := usersList.GetPropertyAttachedToEntity(userName, propertyName) 234 | if err != nil { 235 | return nil, err 236 | } 237 | return data, err 238 | } 239 | -------------------------------------------------------------------------------- /restful/password-restful/pwd_restful_test.go: -------------------------------------------------------------------------------- 1 | package passwordRestful 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "reflect" 10 | "testing" 11 | "time" 12 | 13 | "github.com/emicklei/go-restful" 14 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 15 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 16 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 17 | "github.com/ibm-security-innovation/libsecurity-go/password" 18 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 19 | "github.com/ibm-security-innovation/libsecurity-go/restful/libsecurity-restful" 20 | ) 21 | 22 | const ( 23 | host = "http://localhost" 24 | port = ":8082" 25 | listener = host + port 26 | 27 | userName1 = "User1" 28 | userName2 = "User2" 29 | 30 | secretCode = "1AaB@2345678" 31 | ) 32 | 33 | var ( 34 | propertyName = defs.PwdPropertyName 35 | uData, _ = json.Marshal(secretData{secretCode}) 36 | 37 | resourcePath string // = listener + servicePath + usersPath 38 | usersName = []string{userName1, userName2} 39 | 40 | stRestful *libsecurityRestful.LibsecurityRestful 41 | ) 42 | 43 | func init() { 44 | logger.Init(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard) 45 | 46 | servicePath = cr.ServicePathPrefix + cr.Version + pwdPrefix 47 | resourcePath = listener + servicePath + usersPath 48 | 49 | usersList := en.New() 50 | 51 | stRestful = libsecurityRestful.NewLibsecurityRestful() 52 | stRestful.SetData(usersList, nil, nil, nil, nil) 53 | stRestful.SetToFilterFlag(false) 54 | 55 | for _, name := range usersName { 56 | stRestful.UsersList.AddUser(name) 57 | } 58 | 59 | go runServer() 60 | time.Sleep(100 * time.Millisecond) 61 | } 62 | 63 | func runServer() { 64 | wsContainer := restful.NewContainer() 65 | p := NewPwdRestful() 66 | p.SetData(stRestful) 67 | p.RegisterBasic(wsContainer) 68 | 69 | log.Printf("start listening on %v%v", host, port) 70 | server := &http.Server{Addr: port, Handler: wsContainer} 71 | log.Fatal(server.ListenAndServe()) 72 | } 73 | 74 | func getExpectedData(sData string, okJ interface{}) (string, string, cr.Error, error) { 75 | found, exp, res, e, err := cr.GetExpectedData(sData, okJ) 76 | if found == true { 77 | return exp, res, e, err 78 | } 79 | 80 | switch okJ.(type) { 81 | case *password.UserPwd: // expiration time is not compared 82 | res = cr.RemoveSpaces(string(sData)) 83 | var user password.UserPwd 84 | json.Unmarshal([]byte(sData), &user) 85 | user.Expiration = okJ.(*password.UserPwd).Expiration 86 | if reflect.DeepEqual(user, okJ.(*password.UserPwd)) == false { 87 | data, _ := json.Marshal(okJ.(*password.UserPwd)) 88 | exp = string(data) 89 | } else { 90 | exp = res 91 | } 92 | default: 93 | panic(fmt.Sprintf("Error unknown type: value: %v", okJ)) 94 | } 95 | 96 | if err != nil { 97 | err = json.Unmarshal([]byte(sData), &e) 98 | } 99 | return exp, res, e, err 100 | } 101 | 102 | func exeCommandCheckRes(t *testing.T, method string, url string, expCode int, data string, okJ interface{}) string { 103 | code, sData, err := cr.HTTPDataMethod(method, url, data) 104 | logger.Trace.Println("Method:", method, "Url:", url, "data:", data, "response code:", code, "response data:", sData, "error:", err) 105 | exp, res, e, err := getExpectedData(sData, okJ) 106 | if code != expCode || res != exp || err != nil { 107 | t.Errorf("Test fail: run %v '%v' Expected status: %v, received %v, expected data: '%v' received: '%v', error: %v %v", 108 | method, url, expCode, code, exp, res, e, err) 109 | t.FailNow() 110 | } 111 | return res 112 | } 113 | 114 | func initAListOfUsers(t *testing.T, usersList []string) string { 115 | for _, name := range usersList { 116 | okURLJ := cr.URL{URL: fmt.Sprintf("%v/%v", servicePath, name)} 117 | url := resourcePath + "/" + name 118 | exeCommandCheckRes(t, cr.HTTPPutStr, url, http.StatusCreated, string(uData), okURLJ) 119 | data, _ := stRestful.UsersList.GetPropertyAttachedToEntity(name, propertyName) 120 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusOK, "", data.(*password.UserPwd)) 121 | } 122 | return string(uData) 123 | } 124 | 125 | // Add Ocra property and get it 126 | // Remove the propert and verify an error when try to get it 127 | func Test_addRemovePwd(t *testing.T) { 128 | name := usersName[0] 129 | initAListOfUsers(t, usersName) 130 | 131 | okURLJ := cr.URL{URL: fmt.Sprintf("%v/%v", servicePath, name)} 132 | url := resourcePath + "/" + name 133 | exeCommandCheckRes(t, cr.HTTPPutStr, url, http.StatusCreated, string(uData), okURLJ) 134 | 135 | data, _ := stRestful.UsersList.GetPropertyAttachedToEntity(name, propertyName) 136 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusOK, "", data.(*password.UserPwd)) 137 | 138 | okURLJ = cr.URL{URL: fmt.Sprintf("%v/%v", servicePath, name)} 139 | url = resourcePath + "/" + name 140 | exeCommandCheckRes(t, cr.HTTPDeleteStr, url, http.StatusNoContent, "", cr.StringMessage{Str: ""}) 141 | 142 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusNotFound, "", cr.Error{Code: http.StatusNotFound}) 143 | } 144 | 145 | // 1. Check with match password, verify the results 146 | // 2. Check with not matched password, verify the results 147 | // 3. Update user password and verify that the new password matched 148 | // 4. Verify that the old password not matched 149 | func TestVerifyPassword(t *testing.T) { 150 | userName := usersName[0] 151 | 152 | secret := initAListOfUsers(t, usersName) 153 | 154 | url := listener + servicePath + fmt.Sprintf(cr.ConvertCommandToRequest(urlCommands[verifyUserPasswordCommand]), usersPath, userName) 155 | okURLJ := cr.URL{URL: fmt.Sprintf("%v/%v", servicePath, userName)} 156 | exeCommandCheckRes(t, cr.HTTPPostStr, url, http.StatusOK, secret, cr.Match{Match: true, Message: cr.NoMessageStr}) 157 | 158 | secret1, _ := json.Marshal(secretData{secretCode + "a"}) 159 | exeCommandCheckRes(t, cr.HTTPPostStr, url, http.StatusOK, string(secret1), cr.Match{Match: false, Message: cr.NoMessageStr}) 160 | 161 | secret2, _ := json.Marshal(cr.UpdateSecret{OldPassword: secretCode, NewPassword: secretCode + "a"}) 162 | exeCommandCheckRes(t, cr.HTTPPatchStr, url, http.StatusCreated, string(secret2), okURLJ) 163 | exeCommandCheckRes(t, cr.HTTPPostStr, url, http.StatusOK, string(secret1), cr.Match{Match: true, Message: cr.NoMessageStr}) 164 | exeCommandCheckRes(t, cr.HTTPPostStr, url, http.StatusOK, secret, cr.Match{Match: false, Message: cr.NoMessageStr}) 165 | } 166 | 167 | // 2. Reset the user password 168 | // 3. Check that the new password match only once 169 | // 4. Update user password and verify that the new password matched 170 | func TestVerifyResetPassword(t *testing.T) { 171 | userName := usersName[0] 172 | 173 | initAListOfUsers(t, usersName) 174 | 175 | url := listener + servicePath + fmt.Sprintf(cr.ConvertCommandToRequest(urlCommands[resetUserPasswordCommand]), usersPath, userName, resetUserPwdPath) 176 | secretStr := exeCommandCheckRes(t, cr.HTTPPostStr, url, http.StatusCreated, cr.GetMessageStr, cr.StringMessage{Str: cr.GetMessageStr}) 177 | 178 | url = listener + servicePath + fmt.Sprintf(cr.ConvertCommandToRequest(urlCommands[verifyUserPasswordCommand]), usersPath, userName) 179 | exeCommandCheckRes(t, cr.HTTPPostStr, url, http.StatusOK, secretStr, cr.Match{Match: true, Message: cr.NoMessageStr}) 180 | 181 | secret1, _ := json.Marshal(secretData{secretCode}) 182 | exeCommandCheckRes(t, cr.HTTPPostStr, url, http.StatusOK, string(secret1), cr.Match{Match: false, Message: cr.NoMessageStr}) 183 | } 184 | -------------------------------------------------------------------------------- /accounts/accountManagement_test.go: -------------------------------------------------------------------------------- 1 | package accounts 2 | 3 | import ( 4 | "io/ioutil" 5 | // "fmt" 6 | "testing" 7 | "time" 8 | "os" 9 | 10 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 11 | "github.com/ibm-security-innovation/libsecurity-go/password" 12 | defs "github.com/ibm-security-innovation/libsecurity-go/defs" 13 | ) 14 | 15 | const ( 16 | ) 17 | 18 | var ( 19 | defaultUserName = "User1" 20 | defaultPassword []byte 21 | defaultSalt = []byte("salt123") 22 | secret = []byte("12345678") 23 | ) 24 | 25 | func init() { 26 | logger.Init(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard) 27 | defaultPassword = []byte(password.GenerateNewValidPassword()) 28 | } 29 | 30 | // Test that a new user AM is generated only when all the parameters are valid 31 | func Test_addValidAM(t *testing.T) { 32 | usersName := []string{defaultUserName, ""} 33 | privilege := make(map[string]interface{}) 34 | for k, v := range usersPrivilege { 35 | privilege[k] = v 36 | } 37 | privilege["undef"] = "" 38 | userPwd, _ := password.NewUserPwd(defaultPassword, defaultSalt, true) 39 | pwd := "" 40 | for _, userName := range usersName { 41 | for p := range privilege { 42 | for i := 0; i < password.MaxPasswordLength; i++ { 43 | ok := len(userName) > 0 && 44 | IsValidPrivilege(p) == nil && 45 | userPwd.IsNewPwdValid([]byte(pwd), false) == nil 46 | _, err := NewUserAm(p, []byte(pwd), defaultSalt, false) 47 | if ok == false && err == nil { 48 | t.Errorf("Test fail: Successfully generated new AM with invalid parameters: user name '%v' (%v), privilege '%v' (%v) password '%v' (%v)", 49 | userName, len(userName) != 0, p, IsValidPrivilege(p), pwd, userPwd.IsNewPwdValid([]byte(pwd), false)) 50 | t.FailNow() 51 | } else if ok == true && err != nil { 52 | t.Errorf("Test fail: Error while generated new AM with valid parameters: user name '%v' (%v), privilege '%v' (%v) password '%v' (%v), error: %v", 53 | userName, len(userName) != 0, p, IsValidPrivilege(p), pwd, userPwd.IsNewPwdValid([]byte(pwd), false), err) 54 | t.FailNow() 55 | } 56 | pwd += "a" 57 | } 58 | } 59 | } 60 | } 61 | 62 | // Test that only valid previlege and password can be updated 63 | func Test_updateAM(t *testing.T) { 64 | privilege := make(map[string]interface{}) 65 | for k, v := range usersPrivilege { 66 | privilege[k] = v 67 | } 68 | privilege["undef"] = "" 69 | userPwd, _ := password.NewUserPwd(defaultPassword, defaultSalt, true) 70 | userAm, _ := NewUserAm(SuperUserPermission, defaultPassword, defaultSalt, true) 71 | pwd := "" 72 | for p := range privilege { 73 | for i := 0; i < password.MaxPasswordLength; i++ { 74 | pOk := IsValidPrivilege(p) 75 | pwdOk := userPwd.IsNewPwdValid([]byte(pwd), false) 76 | ok := pOk == nil && pwdOk == nil 77 | updatePOk := userAm.UpdateUserPrivilege(p) 78 | updatePwdOk := userAm.UpdateUserPwd(defaultUserName, userAm.Pwd.Password, []byte(pwd), false) 79 | updateOk := updatePOk == nil && updatePwdOk == nil 80 | if ok == false && updateOk == true { 81 | t.Errorf("Test fail: Successfully updated user AM with invalid parameters: privilege '%v' (%v) password '%v' (%v)", 82 | p, pOk, pwd, pwdOk) 83 | t.FailNow() 84 | } else if ok == true && updateOk == false { 85 | t.Errorf("Test fail: Error while updating user AM with valid parameters: privilege '%v' (%v) password '%v' (%v), error: update privilege: %v, update password %v", 86 | p, pOk, pwd, pwdOk, updatePOk, updatePwdOk) 87 | t.FailNow() 88 | } 89 | pwd += "a" 90 | } 91 | } 92 | } 93 | 94 | // Test that only equal AM returns true 95 | func Test_equalAM(t *testing.T) { 96 | pwd := []string{string(defaultPassword), string(defaultPassword) + "a"} 97 | userAm, _ := NewUserAm(SuperUserPermission, defaultPassword, defaultSalt, true) 98 | userAm1, _ := NewUserAm(SuperUserPermission, defaultPassword, defaultSalt, true) 99 | 100 | if userAm.IsEqual(nil, false) == true { 101 | t.Errorf("Test fail: Unequal AM found equal with nil") 102 | } 103 | for p := range usersPrivilege { 104 | userAm1.UpdateUserPrivilege(p) 105 | for _, pass := range pwd { 106 | userAm1.UpdateUserPwd(defaultUserName, userAm.Pwd.Password, []byte(pass), false) 107 | for exp := 0; exp < 2; exp++ { 108 | if exp > 0 { 109 | userAm1.Pwd.Expiration = time.Now().Add(time.Duration(100*24) * time.Hour) 110 | } else { 111 | userAm1.Pwd.Expiration = userAm.Pwd.Expiration 112 | } 113 | equal := userAm.Privilege == userAm1.Privilege && 114 | string(userAm.Pwd.Password) == string(userAm1.Pwd.Password) 115 | if userAm.IsEqual(userAm1, false) == true && equal == false { 116 | t.Errorf("Test fail: Unequal AM found equal with withExpiration == false: UserAm: '%v'\nuserAm1 '%v'", userAm, userAm1) 117 | } else if userAm.IsEqual(userAm1, true) == true && (equal == false || exp > 0) { 118 | t.Errorf("Test fail: Unequal AM found equal with withExpiration == true: UserAm: '%v'\nuserAm1 '%v'", userAm, userAm1) 119 | } 120 | if userAm.IsEqual(userAm1, false) == false && equal == true { 121 | t.Errorf("Test fail: Equal AM found unequal with withExpiration == false: UserAm: '%v'\nuserAm1 '%v'", userAm, userAm1) 122 | } else if userAm.IsEqual(userAm1, true) == false && (equal == true && exp == 0) { 123 | t.Errorf("Test fail: Equal AM found unequal with withExpiration == true: UserAm: '%v'\nuserAm1 '%v'", userAm, userAm1) 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | // Test that the same password returns true 131 | func Test_IsPasswordMatch(t *testing.T) { 132 | pwd := []string{string(defaultPassword), string(defaultPassword) + "a", ""} 133 | userAm, _ := NewUserAm(SuperUserPermission, defaultPassword, defaultSalt, true) 134 | 135 | for _, pass := range pwd { 136 | err := userAm.IsPasswordMatch([]byte(pass)) 137 | if err == nil && pass != string(defaultPassword) { 138 | t.Errorf("Test fail: curent password '%v' matched wrong password '%v'", string(defaultPassword), pass) 139 | } 140 | if err != nil && pass == string(defaultPassword) { 141 | t.Errorf("Test fail: The same password '%v' wasn't matched '%v', error %v", string(defaultPassword), pass, err) 142 | } 143 | } 144 | } 145 | 146 | func Test_IsPasswordMatch_Should_Not_Allow_Second_Login_With_Temporary_Password(t *testing.T) { 147 | pwd := defaultPassword 148 | 149 | for i:=0 ; i<2 ; i++ { 150 | userAm, _ := NewUserAm(SuperUserPermission, defaultPassword, defaultSalt, true) 151 | if i == 0 { 152 | userAm.Pwd.SetTemporaryPwd(true) 153 | }else { 154 | pwd, _ = userAm.ResetUserPwd() 155 | } 156 | err1 := userAm.IsPasswordMatch(pwd) 157 | if err1 != nil { 158 | t.Errorf("Correct password wasn't matched, error %v", err1) 159 | } 160 | err2 := userAm.IsPasswordMatch(pwd) 161 | if err2 == nil { 162 | t.Errorf("Expected temporary password to fail in second login attempt, after it was used once") 163 | } 164 | } 165 | } 166 | 167 | func Test_StoreLoadAM(t *testing.T) { 168 | userAm, _ := NewUserAm(SuperUserPermission, defaultPassword, defaultSalt, true) 169 | 170 | defs.StoreLoadTest(t, userAm, defs.AmPropertyName) 171 | } 172 | 173 | // Test corners: String, logger etc 174 | func Test_corners(t *testing.T) { 175 | userAm, _ := NewUserAm(SuperUserPermission, defaultPassword, defaultSalt, true) 176 | logger.Init(ioutil.Discard, ioutil.Discard, os.Stdout, os.Stderr) 177 | logger.Trace.Println("The user info is", userAm.String()) 178 | privileges := GetUsersPrivilege() 179 | for p := range privileges { 180 | if p != UserPermission && p != SuperUserPermission && p != AdminPermission { 181 | t.Errorf("Unknown permission index '%v'", p) 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /swagger-dist/lib/shred/content.js: -------------------------------------------------------------------------------- 1 | 2 | // The purpose of the `Content` object is to abstract away the data conversions 3 | // to and from raw content entities as strings. For example, you want to be able 4 | // to pass in a Javascript object and have it be automatically converted into a 5 | // JSON string if the `content-type` is set to a JSON-based media type. 6 | // Conversely, you want to be able to transparently get back a Javascript object 7 | // in the response if the `content-type` is a JSON-based media-type. 8 | 9 | // One limitation of the current implementation is that it [assumes the `charset` is UTF-8](https://github.com/spire-io/shred/issues/5). 10 | 11 | // The `Content` constructor takes an options object, which *must* have either a 12 | // `body` or `data` property and *may* have a `type` property indicating the 13 | // media type. If there is no `type` attribute, a default will be inferred. 14 | var Content = function(options) { 15 | this.body = options.body; 16 | this.data = options.data; 17 | this.type = options.type; 18 | }; 19 | 20 | Content.prototype = { 21 | // Treat `toString()` as asking for the `content.body`. That is, the raw content entity. 22 | // 23 | // toString: function() { return this.body; } 24 | // 25 | // Commented out, but I've forgotten why. :/ 26 | }; 27 | 28 | 29 | // `Content` objects have the following attributes: 30 | Object.defineProperties(Content.prototype,{ 31 | 32 | // - **type**. Typically accessed as `content.type`, reflects the `content-type` 33 | // header associated with the request or response. If not passed as an options 34 | // to the constructor or set explicitly, it will infer the type the `data` 35 | // attribute, if possible, and, failing that, will default to `text/plain`. 36 | type: { 37 | get: function() { 38 | if (this._type) { 39 | return this._type; 40 | } else { 41 | if (this._data) { 42 | switch(typeof this._data) { 43 | case "string": return "text/plain"; 44 | case "object": return "application/json"; 45 | } 46 | } 47 | } 48 | return "text/plain"; 49 | }, 50 | set: function(value) { 51 | this._type = value; 52 | return this; 53 | }, 54 | enumerable: true 55 | }, 56 | 57 | // - **data**. Typically accessed as `content.data`, reflects the content entity 58 | // converted into Javascript data. This can be a string, if the `type` is, say, 59 | // `text/plain`, but can also be a Javascript object. The conversion applied is 60 | // based on the `processor` attribute. The `data` attribute can also be set 61 | // directly, in which case the conversion will be done the other way, to infer 62 | // the `body` attribute. 63 | data: { 64 | get: function() { 65 | if (this._body) { 66 | return this.processor.parser(this._body); 67 | } else { 68 | return this._data; 69 | } 70 | }, 71 | set: function(data) { 72 | if (this._body&&data) Errors.setDataWithBody(this); 73 | this._data = data; 74 | return this; 75 | }, 76 | enumerable: true 77 | }, 78 | 79 | // - **body**. Typically accessed as `content.body`, reflects the content entity 80 | // as a UTF-8 string. It is the mirror of the `data` attribute. If you set the 81 | // `data` attribute, the `body` attribute will be inferred and vice-versa. If 82 | // you attempt to set both, an exception is raised. 83 | body: { 84 | get: function() { 85 | if (this._data) { 86 | return this.processor.stringify(this._data); 87 | } else { 88 | return this._body.toString(); 89 | } 90 | }, 91 | set: function(body) { 92 | if (this._data&&body) Errors.setBodyWithData(this); 93 | this._body = body; 94 | return this; 95 | }, 96 | enumerable: true 97 | }, 98 | 99 | // - **processor**. The functions that will be used to convert to/from `data` and 100 | // `body` attributes. You can add processors. The two that are built-in are for 101 | // `text/plain`, which is basically an identity transformation and 102 | // `application/json` and other JSON-based media types (including custom media 103 | // types with `+json`). You can add your own processors. See below. 104 | processor: { 105 | get: function() { 106 | var processor = Content.processors[this.type]; 107 | if (processor) { 108 | return processor; 109 | } else { 110 | // Return the first processor that matches any part of the 111 | // content type. ex: application/vnd.foobar.baz+json will match json. 112 | var main = this.type.split(";")[0]; 113 | var parts = main.split(/\+|\//); 114 | for (var i=0, l=parts.length; i < l; i++) { 115 | processor = Content.processors[parts[i]] 116 | } 117 | return processor || {parser:identity,stringify:toString}; 118 | } 119 | }, 120 | enumerable: true 121 | }, 122 | 123 | // - **length**. Typically accessed as `content.length`, returns the length in 124 | // bytes of the raw content entity. 125 | length: { 126 | get: function() { 127 | if (typeof Buffer !== 'undefined') { 128 | return Buffer.byteLength(this.body); 129 | } 130 | return this.body.length; 131 | } 132 | } 133 | }); 134 | 135 | Content.processors = {}; 136 | 137 | // The `registerProcessor` function allows you to add your own processors to 138 | // convert content entities. Each processor consists of a Javascript object with 139 | // two properties: 140 | // - **parser**. The function used to parse a raw content entity and convert it 141 | // into a Javascript data type. 142 | // - **stringify**. The function used to convert a Javascript data type into a 143 | // raw content entity. 144 | Content.registerProcessor = function(types,processor) { 145 | 146 | // You can pass an array of types that will trigger this processor, or just one. 147 | // We determine the array via duck-typing here. 148 | if (types.forEach) { 149 | types.forEach(function(type) { 150 | Content.processors[type] = processor; 151 | }); 152 | } else { 153 | // If you didn't pass an array, we just use what you pass in. 154 | Content.processors[types] = processor; 155 | } 156 | }; 157 | 158 | // Register the identity processor, which is used for text-based media types. 159 | var identity = function(x) { return x; } 160 | , toString = function(x) { return x.toString(); } 161 | Content.registerProcessor( 162 | ["text/html","text/plain","text"], 163 | { parser: identity, stringify: toString }); 164 | 165 | // Register the JSON processor, which is used for JSON-based media types. 166 | Content.registerProcessor( 167 | ["application/json; charset=utf-8","application/json","json"], 168 | { 169 | parser: function(string) { 170 | return JSON.parse(string); 171 | }, 172 | stringify: function(data) { 173 | return JSON.stringify(data); }}); 174 | 175 | var qs = require('querystring'); 176 | // Register the post processor, which is used for JSON-based media types. 177 | Content.registerProcessor( 178 | ["application/x-www-form-urlencoded"], 179 | { parser : qs.parse, stringify : qs.stringify }); 180 | 181 | // Error functions are defined separately here in an attempt to make the code 182 | // easier to read. 183 | var Errors = { 184 | setDataWithBody: function(object) { 185 | throw new Error("Attempt to set data attribute of a content object " + 186 | "when the body attributes was already set."); 187 | }, 188 | setBodyWithData: function(object) { 189 | throw new Error("Attempt to set body attribute of a content object " + 190 | "when the data attributes was already set."); 191 | } 192 | } 193 | module.exports = Content; -------------------------------------------------------------------------------- /restful/storage-restful/secureStorage_restful_test.go: -------------------------------------------------------------------------------- 1 | package storageRestful 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | "time" 12 | 13 | "github.com/emicklei/go-restful" 14 | en "github.com/ibm-security-innovation/libsecurity-go/entity" 15 | logger "github.com/ibm-security-innovation/libsecurity-go/logger" 16 | cr "github.com/ibm-security-innovation/libsecurity-go/restful/common-restful" 17 | "github.com/ibm-security-innovation/libsecurity-go/restful/libsecurity-restful" 18 | ss "github.com/ibm-security-innovation/libsecurity-go/storage" 19 | ) 20 | 21 | const ( 22 | host = "http://localhost" 23 | port = ":8082" 24 | listener = host + port 25 | 26 | secretCode = "aBCc1@234567890123456" 27 | emptyRes = "{}" 28 | ) 29 | 30 | type headerMapT map[string]string 31 | 32 | var ( 33 | resourcePath string // = listener + servicePath + storagePath 34 | itemPath string // = listener + servicePath + storageItemPath 35 | 36 | baseHeaderInfo = make(headerMapT) 37 | stRestful *libsecurityRestful.LibsecurityRestful 38 | ) 39 | 40 | func init() { 41 | logger.Init(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard) 42 | 43 | servicePath = cr.ServicePathPrefix + cr.Version + sPrefix 44 | resourcePath = listener + servicePath + storagePath 45 | itemPath = listener + servicePath + storageItemPath 46 | 47 | baseHeaderInfo[secretIDParam] = secretCode 48 | 49 | usersList := en.New() 50 | stRestful = libsecurityRestful.NewLibsecurityRestful() 51 | secureStorage, _ := ss.NewStorage([]byte(secretCode), true) 52 | stRestful.SetData(usersList, nil, nil, nil, secureStorage) 53 | stRestful.SetToFilterFlag(false) 54 | 55 | go runServer() 56 | time.Sleep(100 * time.Millisecond) 57 | } 58 | 59 | func runServer() { 60 | wsContainer := restful.NewContainer() 61 | s := NewSsRestful() 62 | s.SetData(stRestful) 63 | s.RegisterBasic(wsContainer) 64 | 65 | log.Printf("start listening on %v%v", host, port) 66 | server := &http.Server{Addr: port, Handler: wsContainer} 67 | log.Fatal(server.ListenAndServe()) 68 | } 69 | 70 | func getExpectedData(sData string, okJ interface{}) (string, string, cr.Error, error) { 71 | found, exp, res, e, err := cr.GetExpectedData(sData, okJ) 72 | if found == true { 73 | return exp, res, e, err 74 | } 75 | 76 | switch okJ.(type) { 77 | case itemValue: 78 | var val itemValue 79 | err = json.Unmarshal([]byte(sData), &val) 80 | res = fmt.Sprintf("%v", val.Data) 81 | exp = fmt.Sprintf("%v", okJ.(itemValue).Data) 82 | case ss.SecureStorage: 83 | var data ss.SecureStorage 84 | err = json.Unmarshal([]byte(sData), &data) 85 | res = fmt.Sprintf("%v", data.Data) 86 | exp = fmt.Sprintf("%v", okJ.(ss.SecureStorage).Data) 87 | default: 88 | panic(fmt.Sprintf("Error unknown type: value: %v", okJ)) 89 | } 90 | 91 | if err != nil { 92 | err = json.Unmarshal([]byte(sData), &e) 93 | } 94 | return exp, res, e, err 95 | } 96 | 97 | func HTTPDataMethodWithHeader(method string, url string, data string, headerInfo headerMapT) (int, string, error) { 98 | client := &http.Client{} 99 | request, err := http.NewRequest(method, url, strings.NewReader(data)) 100 | request.Header.Set("Content-Type", "application/json") 101 | for key, value := range headerInfo { 102 | request.Header.Set(key, value) 103 | } 104 | request.AddCookie(&http.Cookie{Name: cr.AccessToken, Value: "", Path: "/"}) 105 | response, err := client.Do(request) 106 | return cr.GetResponse(response, err) 107 | } 108 | 109 | func exeCommandCheckRes(t *testing.T, method string, url string, expCode int, data string, headerInfo headerMapT, okJ interface{}) string { 110 | code, sData, err := HTTPDataMethodWithHeader(method, url, data, headerInfo) 111 | logger.Trace.Println("Method:", method, "Url:", url, "data:", data, "header info:", headerInfo, 112 | "response code:", code, "response data:", sData, "error:", err) 113 | exp, res, e, err := getExpectedData(sData, okJ) 114 | if code != expCode || res != exp || err != nil { 115 | t.Errorf("Test fail: run %v '%v' Expected status: %v, received %v, expected data: '%v' received: '%v', error: %v %v", 116 | method, url, expCode, code, exp, res, e, err) 117 | t.FailNow() 118 | } 119 | return res 120 | } 121 | 122 | func initState(t *testing.T) { 123 | headerInfo := make(headerMapT) 124 | headerInfo[secretIDParam] = secretCode 125 | url := listener + servicePath + fmt.Sprintf(cr.ConvertCommandToRequest(urlCommands[handleStorageCommand]), storagePath) 126 | okURLJ := cr.URL{URL: fmt.Sprintf("%v", servicePath)} 127 | exeCommandCheckRes(t, cr.HTTPPutStr, url, http.StatusCreated, "", baseHeaderInfo, okURLJ) 128 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusOK, cr.GetMessageStr, baseHeaderInfo, cr.StringMessage{Str: cr.GetMessageStr}) 129 | } 130 | 131 | // Test the following functions: add/get/delete item to/from storage and get storage 132 | // 1. Create a storage, and 2 key-value to the storage 133 | // 2. Get the items and verify their values 134 | // 3. Get the storage information and compare to the expected data 135 | // 4. Delete the items and verify that it is not in the storage 136 | // 5. Remove storage and verify that the list is empty 137 | func TestAddGetDeleteItem(t *testing.T) { 138 | keys := []string{"data1", "data2"} 139 | values := []string{"value1", "value2"} 140 | headerInfo := make(headerMapT) 141 | 142 | headerInfo[secretIDParam] = secretCode 143 | initState(t) 144 | okURLJ := cr.URL{URL: fmt.Sprintf("%v", servicePath)} 145 | for i, key := range keys { 146 | url := itemPath 147 | item, _ := json.Marshal(itemData{key, values[i]}) 148 | exeCommandCheckRes(t, cr.HTTPPatchStr, url, http.StatusCreated, string(item), baseHeaderInfo, okURLJ) 149 | headerInfo[keyIDParam] = key 150 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusOK, "", headerInfo, itemValue{values[i]}) 151 | } 152 | 153 | for i, key := range keys { 154 | url := itemPath 155 | headerInfo[keyIDParam] = key 156 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusOK, "", headerInfo, itemValue{values[i]}) 157 | exeCommandCheckRes(t, cr.HTTPDeleteStr, url, http.StatusNoContent, "", headerInfo, cr.EmptyStr) 158 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusNotFound, "", headerInfo, cr.Error{Code: http.StatusNotFound}) 159 | } 160 | url := fmt.Sprintf(cr.ConvertCommandToRequest(urlCommands[handleStorageCommand]), resourcePath) 161 | exeCommandCheckRes(t, cr.HTTPDeleteStr, url, http.StatusNoContent, "", baseHeaderInfo, cr.EmptyStr) 162 | } 163 | 164 | // 1. Verify that secure storage with simple password cannot be created 165 | // 2. Verify that get of undefined secure storage return an error 166 | // 3. Verify that delete of undefined secure storage return an error 167 | // 4. VErify that add/get/delete item to secure storage with the wrong secret cannot be done 168 | func TestErrors(t *testing.T) { 169 | headerInfo := make(headerMapT) 170 | headerInfo[secretIDParam] = "1234" 171 | url := listener + servicePath + fmt.Sprintf(cr.ConvertCommandToRequest(urlCommands[handleStorageCommand]), storagePath) 172 | exeCommandCheckRes(t, cr.HTTPPutStr, url, http.StatusBadRequest, "", headerInfo, cr.StringMessage{Str: cr.GetMessageStr}) 173 | 174 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusNotFound, cr.GetMessageStr, headerInfo, cr.StringMessage{Str: cr.GetMessageStr}) 175 | exeCommandCheckRes(t, cr.HTTPDeleteStr, url, http.StatusNotFound, cr.GetMessageStr, headerInfo, cr.StringMessage{Str: cr.GetMessageStr}) 176 | 177 | url = itemPath 178 | item, _ := json.Marshal(itemData{"123", "1"}) 179 | exeCommandCheckRes(t, cr.HTTPPatchStr, url, http.StatusNotFound, string(item), headerInfo, cr.StringMessage{Str: cr.GetMessageStr}) 180 | exeCommandCheckRes(t, cr.HTTPGetStr, url, http.StatusNotFound, string(item), headerInfo, cr.StringMessage{Str: cr.GetMessageStr}) 181 | exeCommandCheckRes(t, cr.HTTPDeleteStr, url, http.StatusNotFound, string(item), headerInfo, cr.StringMessage{Str: cr.GetMessageStr}) 182 | } --------------------------------------------------------------------------------